Skip to content

Conversation

@quickcoffee
Copy link
Contributor

@quickcoffee quickcoffee commented Aug 29, 2025

This is a first attempt for #1944.
I tested it locally with a failed model and a failed test:
image
image

I'm not quite sure if the tests that Claude Code created make that much sense - looking forward to some feedback here.

Summary by CodeRabbit

  • New Features

    • Alerts now carry orchestrator context: job and run identifiers, URLs, and orchestrator name propagated through generation to rendering.
    • Alert subtitles and messages can display an inline "Job" line and deep links to runs/jobs; link icon (🔗) added to icon set.
  • Tests

    • Added tests for orchestrator info extraction, job/run link creation, and message rendering with and without orchestrator data.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Aug 29, 2025

📝 Walkthrough

Walkthrough

Adds optional job/orchestrator fields to alert payloads, exposes them via AlertModel.orchestrator_info, adds Icon.LINK and its Unicode mapping, provides orchestrator link utilities, renders job/orchestrator links in alert subtitles, updates dbt macros/models to populate invocation fields, and adds unit tests. (49 words)

Changes

Cohort / File(s) Summary
Icons & Unicode
elementary/messages/blocks.py, elementary/messages/formats/unicode.py
Added Icon.LINK enum member and mapped it in ICON_TO_UNICODE to "🔗".
Alert models & schemas
elementary/monitor/alerts/alert.py, elementary/monitor/alerts/model_alert.py, elementary/monitor/alerts/source_freshness_alert.py, elementary/monitor/alerts/test_alert.py, elementary/monitor/fetchers/alerts/schema/alert_data.py
Added optional fields job_id, job_name, job_run_id, job_url, job_run_url, orchestrator to schemas/constructors; added AlertModel.orchestrator_info property; subclasses forward new params.
Alert message builder
elementary/monitor/alerts/alert_messages/builder.py
Extended subtitle builder to accept orchestrator_info, render inline "Job" line when present, and aggregate/emit orchestrator/job links into subtitle blocks.
Orchestrator link utilities (new)
elementary/monitor/data_monitoring/alerts/integrations/utils/orchestrator_link.py
New OrchestratorLinkData pydantic model plus create_orchestrator_link and create_job_link helpers producing optional link metadata (with icons) from OrchestratorInfo.
DBT macros & models
elementary/monitor/dbt_project/macros/alerts/population/*.sql, .../get_source_freshness_results.sql, .../utils/normalized_source_freshness_status.sql, elementary/monitor/dbt_project/models/alerts/alerts_v2.sql
Joined dbt_run_results/dbt_invocations, added invocation/job fields to alert payloads, tightened status references, parameterized normalized freshness macro, and added model dependencies on invocation/run-results.
Tests & fixtures
tests/unit/alerts/alert_messages/test_orchestrator_message_simple.py, tests/unit/alerts/test_orchestrator_integration.py, tests/unit/messages/formats/*/fixtures/all_icons.*
Added tests for orchestrator_info, create_orchestrator_link/create_job_link, and alert message rendering; updated icon fixtures to include LINK 🔗.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant DBT as DBT (macros/models)
  participant Fetcher as Fetcher (alert_data)
  participant Alert as AlertModel
  participant Builder as AlertMessageBuilder
  participant Utils as OrchestratorLinkUtils
  participant Renderer as Message Renderer

  DBT->>Fetcher: emit rows including invocation fields
  Fetcher->>Alert: construct alert with job/orchestrator fields
  Alert-->>Builder: orchestrator_info (OrchestratorInfo or None)
  alt orchestrator_info present
    Builder->>Utils: create_job_link(orchestrator_info)
    Builder->>Utils: create_orchestrator_link(orchestrator_info)
    Utils-->>Builder: OrchestratorLinkData (optional)
    Builder->>Renderer: LinesBlock (inline Job line + links)
  else no orchestrator_info
    Builder->>Renderer: LinesBlock (summary + existing links)
  end
  Renderer-->>Builder: formatted message (e.g., Block Kit)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

I hop with joy, a link in paw 🔗
Jobs and runs now show where they are.
Gears and clouds in tidy sight,
Alerts now point you to the right.
A rabbit cheers — follow the star 🐇

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.81% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding orchestrator jobs data to alerts, which is reflected consistently across all modified files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 77dbc41 and 0cb6f73.

📒 Files selected for processing (5)
  • tests/unit/messages/formats/adaptive_cards/fixtures/all_icons.json
  • tests/unit/messages/formats/block_kit/fixtures/all_icons.json
  • tests/unit/messages/formats/html/fixtures/all_icons.html
  • tests/unit/messages/formats/markdown/fixtures/all_icons.md
  • tests/unit/messages/formats/text/fixtures/all_icons.txt
🔇 Additional comments (5)
tests/unit/messages/formats/text/fixtures/all_icons.txt (1)

1-1: LGTM! Fixture correctly updated with the new LINK icon.

The addition of "LINK 🔗" to the icon sequence is consistent with the new Icon.LINK enum introduced in this PR. This fixture update aligns with the expected test output after running tests with OVERRIDE=true, as mentioned in the PR comments.

tests/unit/messages/formats/block_kit/fixtures/all_icons.json (1)

7-7: LGTM! Unicode escape for LINK icon is correct.

The addition of "LINK \ud83d\udd17" to the Block Kit fixture is correct. The Unicode escape sequence \ud83d\udd17 properly represents the 🔗 emoji (U+1F517) and follows the same pattern as other icons in the sequence.

tests/unit/messages/formats/markdown/fixtures/all_icons.md (1)

1-1: LGTM! Markdown fixture correctly updated.

The LINK icon addition is consistent with other format fixtures and properly reflects the new Icon.LINK functionality introduced in this PR.

tests/unit/messages/formats/adaptive_cards/fixtures/all_icons.json (1)

10-10: LGTM! Adaptive Cards fixture correctly updated.

The Unicode escape for the LINK icon is correct and consistent with the Block Kit fixture. The addition properly reflects the new Icon.LINK functionality.

tests/unit/messages/formats/html/fixtures/all_icons.html (1)

1-1: LGTM! HTML fixture correctly updated with consistent structure.

The LINK icon addition follows the same HTML structure pattern as other icons in the fixture (wrapped in a span with margin-right:4px styling), and the 🔗 emoji is properly included.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Contributor

👋 @quickcoffee
Thank you for raising your pull request.
Please make sure to add tests and document all user-facing changes.
You can do this by editing the docs files in this pull request.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
elementary/monitor/dbt_project/macros/get_source_freshness_results.sql (1)

43-53: Field mapping bugs: owners and filter keys are mismatched.

  • owners: SELECT aliases sources.owner as owners but later reads get('owner').
  • filter fallback: SELECT aliases source_freshness_filter but later reads get('freshness_filter').

Both cause data loss in the returned dict.

-            sources.owner as owners,
+            sources.owner as owners,
@@
-                                 'owners': source_freshness_result_dict.get('owner'),
+                                 'owners': source_freshness_result_dict.get('owners'),
@@
-                                 'filter': filter if filter is not none else source_freshness_result_dict.get('freshness_filter'),
+                                 'filter': filter if filter is not none else source_freshness_result_dict.get('source_freshness_filter'),

Also applies to: 95-95, 99-99

🧹 Nitpick comments (12)
elementary/monitor/dbt_project/macros/utils/normalized_source_freshness_status.sql (1)

1-7: Make normalization robust to case/whitespace.

Current comparisons are case-sensitive. Trim/lower the input to avoid surprises from upstream variations.

-{% macro normalized_source_freshness_status(status_column='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 trim(lower({{ status_column }})) = 'error' then 'fail'
+        when trim(lower({{ status_column }})) = 'runtime error' then 'error'
+        else {{ status_column }}
     end as normalized_status
 {% endmacro %}
elementary/monitor/alerts/source_freshness_alert.py (1)

49-55: Forwarding orchestrator fields — good; include them in .data for parity.

Fields are accepted and forwarded to the base. Expose them in data to keep serialization consistent across alert types.

         return dict(
             id=self.id,
             alert_class_id=self.alert_class_id,
             model_unique_id=self.model_unique_id,
             detected_at=self.detected_at_str,
             database_name=self.database_name,
             schema_name=self.schema_name,
             owners=self.owners,
             tags=self.tags,
             subscribers=self.subscribers,
             status=self.status,
             suppression_interval=self.suppression_interval,
             source_name=self.source_name,
             identifier=self.identifier,
             original_status=self.original_status,
             error_after=self.error_after,
             warn_after=self.warn_after,
             path=self.path,
             error=self.error,
             snapshotted_at=self.snapshotted_at_str,
             max_loaded_at=self.max_loaded_at,
             max_loaded_at_time_ago_in_s=self.max_loaded_at_time_ago_in_s,
             filter=self.filter,
             freshness_description=self.freshness_description,
+            orchestrator=getattr(self, "orchestrator", None),
+            job_id=getattr(self, "job_id", None),
+            job_name=getattr(self, "job_name", None),
+            job_run_id=getattr(self, "job_run_id", None),
+            job_url=getattr(self, "job_url", None),
+            job_run_url=getattr(self, "job_run_url", None),
         )

If downstream renderers rely on alert.orchestrator_info instead, confirm they don’t expect it inside data.

Also applies to: 75-81

elementary/monitor/dbt_project/macros/alerts/population/source_freshness_alerts.sql (1)

132-133: Consider filtering by normalized status for consistency.
Using raw results.status may miss adapter-specific variants; prefer the normalized form if available.

Possible change:

-    where lower(results.status) != 'pass'
+    where lower({{ elementary_cli.normalized_source_freshness_status('results.status') }}) != 'pass'
elementary/monitor/dbt_project/macros/alerts/population/test_alerts.sql (2)

123-124: Explicit status filter is correct; consider normalization.
Optional: normalize status to be adapter-agnostic.


162-169: Join may duplicate alerts if multiple run results per execution — verify and dedupe if needed.
If duplication occurs, select the latest invocation per test_execution_id (e.g., window/QUALIFY).

Also applies to: 172-173

elementary/monitor/dbt_project/macros/alerts/population/model_alerts.sql (1)

145-151: Join/run invocation wiring — verify 1:1 mapping and dedupe if necessary.
If multiple run_result rows exist per alert_id, consider windowing to latest invocation before the join.

Also applies to: 154-155

elementary/monitor/data_monitoring/alerts/integrations/utils/orchestrator_link.py (2)

14-29: Use display-friendly orchestrator names consistently.
create_orchestrator_link should mirror the title-casing used in create_job_link.

-    orchestrator = orchestrator_info.get("orchestrator", "orchestrator")
+    orchestrator = orchestrator_info.get("orchestrator", "orchestrator")
+    display_name = orchestrator.replace("_", " ").title()
 ...
-        text=f"View in {orchestrator}",
+        text=f"View in {display_name}",

31-50: Fallback to job_id when job_name is missing; minor typing polish.
Improves robustness for orchestrators that don’t populate names.

-    job_name = orchestrator_info.get("job_name", "Job")
+    job_name = orchestrator_info.get("job_name") or orchestrator_info.get("job_id") or "Job"

Optional: change parameter type to Mapping[str, str] for flexibility.

tests/unit/alerts/alert_messages/test_orchestrator_message_simple.py (1)

91-98: Consider asserting structure rather than string repr.

Matching on str(slack_message) is brittle. Prefer asserting presence of a LinkBlock with the expected text/URL.

Example:

-        slack_message = format_block_kit(message_body)
-        slack_json = str(slack_message)
-        assert "View in Dbt Cloud" in slack_json
+        slack_message = format_block_kit(message_body)
+        # walk blocks to find any LinkBlock with expected text
+        assert any(
+            getattr(inline, "text", None) == "View in Dbt Cloud"
+            for block in slack_message.blocks
+            for line in getattr(block, "lines", [])
+            for inline in getattr(line, "inlines", [])
+        )
elementary/monitor/alerts/alert_messages/builder.py (1)

104-114: Avoid mutable default for links and rename param to avoid shadowing built-in.

Use links: Optional[list[ReportLinkData]] = None and set inside. Also type shadows the built-in; prefer asset_type.

Apply:

-    def _get_run_alert_subtitle_block(
-        self,
-        type: Literal["test", "snapshot", "model", "source"],
+    def _get_run_alert_subtitle_block(
+        self,
+        asset_type: Literal["test", "snapshot", "model", "source"],
         name: str,
         status: Optional[str] = None,
         detected_at_str: Optional[str] = None,
         suppression_interval: Optional[int] = None,
         env: Optional[str] = None,
-        links: list[ReportLinkData] = [],
+        links: Optional[list[ReportLinkData]] = None,
         orchestrator_info: Optional[Dict[str, str]] = None,
     ) -> LinesBlock:
+        links = links or []
         summary = []
-        summary.append((type.capitalize() + ":", name))
+        summary.append((asset_type.capitalize() + ":", name))

And update the call site below:

-                type=asset_type,
+                asset_type=asset_type,
elementary/monitor/alerts/model_alert.py (2)

66-73: Forwarding to base with explicit keywords — LGTM.

Keeps signature parity and future-proofs against ordering drift in AlertModel.

If you want to reduce positional coupling further, consider switching the earlier positional args to keyword args in the super().__init__ call for clarity and resilience.


81-101: Confirm whether orchestrator fields must be surfaced in data.

data omits orchestrator, job_id, job_run_id, job_url, job_run_url. If downstream serialization/templates rely solely on AlertModel.data, consider adding them; if renderers read from orchestrator_info on the instance, current behavior is fine. Please verify consumers.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 948b8b0 and 476fae6.

📒 Files selected for processing (17)
  • elementary/messages/blocks.py (1 hunks)
  • elementary/messages/formats/unicode.py (1 hunks)
  • elementary/monitor/alerts/alert.py (3 hunks)
  • elementary/monitor/alerts/alert_messages/builder.py (6 hunks)
  • elementary/monitor/alerts/model_alert.py (2 hunks)
  • elementary/monitor/alerts/source_freshness_alert.py (2 hunks)
  • elementary/monitor/alerts/test_alert.py (2 hunks)
  • elementary/monitor/data_monitoring/alerts/integrations/utils/orchestrator_link.py (1 hunks)
  • elementary/monitor/dbt_project/macros/alerts/population/model_alerts.sql (4 hunks)
  • elementary/monitor/dbt_project/macros/alerts/population/source_freshness_alerts.sql (4 hunks)
  • elementary/monitor/dbt_project/macros/alerts/population/test_alerts.sql (4 hunks)
  • elementary/monitor/dbt_project/macros/get_source_freshness_results.sql (1 hunks)
  • elementary/monitor/dbt_project/macros/utils/normalized_source_freshness_status.sql (1 hunks)
  • elementary/monitor/dbt_project/models/alerts/alerts_v2.sql (1 hunks)
  • elementary/monitor/fetchers/alerts/schema/alert_data.py (4 hunks)
  • tests/unit/alerts/alert_messages/test_orchestrator_message_simple.py (1 hunks)
  • tests/unit/alerts/test_orchestrator_integration.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
elementary/monitor/data_monitoring/alerts/integrations/utils/orchestrator_link.py (2)
elementary/messages/blocks.py (1)
  • Icon (8-23)
elementary/monitor/alerts/alert.py (1)
  • orchestrator_info (101-118)
elementary/messages/formats/unicode.py (1)
elementary/messages/blocks.py (1)
  • Icon (8-23)
tests/unit/alerts/test_orchestrator_integration.py (3)
elementary/messages/blocks.py (1)
  • Icon (8-23)
elementary/monitor/data_monitoring/alerts/integrations/utils/orchestrator_link.py (3)
  • OrchestratorLinkData (7-11)
  • create_job_link (31-49)
  • create_orchestrator_link (14-28)
elementary/monitor/alerts/alert.py (1)
  • orchestrator_info (101-118)
tests/unit/alerts/alert_messages/test_orchestrator_message_simple.py (5)
elementary/messages/formats/block_kit.py (1)
  • format_block_kit (360-364)
elementary/monitor/alerts/model_alert.py (2)
  • ModelAlertModel (14-116)
  • get_report_link (115-116)
elementary/monitor/alerts/test_alert.py (2)
  • TestAlertModel (13-201)
  • get_report_link (200-201)
tests/unit/alerts/alert_messages/test_alert_utils.py (1)
  • get_alert_message_body (305-316)
elementary/monitor/alerts/alert.py (2)
  • get_report_link (97-98)
  • orchestrator_info (101-118)
elementary/monitor/alerts/alert_messages/builder.py (4)
elementary/messages/block_builders.py (4)
  • BoldTextBlock (63-64)
  • LinkInlineBlocks (85-92)
  • SummaryLineBlock (109-122)
  • LinksLineBlock (99-106)
elementary/monitor/data_monitoring/alerts/integrations/utils/orchestrator_link.py (1)
  • create_orchestrator_link (14-28)
elementary/monitor/alerts/alert.py (2)
  • orchestrator_info (101-118)
  • summary (94-95)
elementary/messages/blocks.py (2)
  • TextBlock (40-43)
  • LineBlock (67-70)
🔇 Additional comments (29)
elementary/monitor/dbt_project/models/alerts/alerts_v2.sql (1)

27-29: Confirm upstream model existence and DAG ordering
The -- depends_on refs in alerts_v2.sql correctly declare dependencies on the two Elementary package models. Since dbt_invocations and dbt_run_results live in the external elementary package, verify that your pinned package version includes them and that they appear upstream of alerts_v2 by running:

dbt ls --select +elementary.alerts_v2 --resource-type model --output name | sed -n '1,50p'

Ensure both elementary.dbt_invocations and elementary.dbt_run_results are listed before alerts_v2.

elementary/messages/blocks.py (1)

23-23: LINK icon mapping verified across all outputs
Icon.LINK is present in ICON_TO_UNICODE, and the text, markdown, adaptive_cards, and block_kit formatters all delegate to it. There is no HTML-specific formatter to update.

elementary/monitor/dbt_project/macros/get_source_freshness_results.sql (1)

13-13: Alignment with new macro signature — OK.

Passing 'status' explicitly matches the updated macro and keeps intent clear.

elementary/messages/formats/unicode.py (1)

18-19: LGTM — mapping added and coverage check retained.

The 🔗 mapping prevents runtime errors from the coverage guard below.

elementary/monitor/dbt_project/macros/alerts/population/source_freshness_alerts.sql (3)

36-43: Good addition: job/orchestrator fields are plumbed into the payload.
Matches the new AlertModel fields; no issues spotted.


95-95: Ensure the normalized status has a stable alias.
populate_source_freshness_alerts later reads normalized_status. If the macro doesn’t alias, add one here.

Apply if needed:

-      {{ elementary_cli.normalized_source_freshness_status('results.status')}},
+      {{ elementary_cli.normalized_source_freshness_status('results.status') }} as normalized_status,

130-131: Confirm join cardinality on model_execution_id
The LEFT JOIN dbt_run_results ON results.source_freshness_execution_id = dbt_run_results.model_execution_id (source_freshness_alerts.sql lines 130–131) assumes each model_execution_id appears only once. If duplicates exist in dbt_run_results, alerts will fan out. Verify the underlying dbt_run_results model enforces a one-row-per-execution_id constraint or add a deduplication step.

elementary/monitor/dbt_project/macros/alerts/population/test_alerts.sql (2)

42-49: Payload enriched with job/orchestrator context — looks good.
Keys align with AlertModel/orchestrator_info.


83-90: Confirm availability of invocation/run results models across environments
The dbt_invocations and dbt_run_results models are defined in the package under elementary/monitor/dbt_project/models (e.g. models/dbt_invocations.sql and models/dbt_run_results.sql), and are referenced by macros in alerts and tests. Ensure these files are indeed present in all adapter-specific subfolders (if any) and included in every deployed environment.

elementary/monitor/alerts/alert.py (1)

35-40: Constructor now carries orchestrator/job fields — OK.
Field names match downstream usage and schema.

Also applies to: 74-80

elementary/monitor/dbt_project/macros/alerts/population/model_alerts.sql (3)

24-31: Model alert payload gains orchestrator/job fields — good alignment.
No structural issues.


120-125: Filter scoping fixed to all_run_results — good.
Time window and status predicates look correct.


132-132: detected_at sourced from the right alias — good fix.
Prevents timezone/alias confusion.

tests/unit/alerts/test_orchestrator_integration.py (5)

67-81: LGTM: job link expectations match implementation.

Text, URL, and Icon.GEAR align with create_job_link.


122-141: LGTM: minimal orchestrator info handled correctly.

orchestrator_info includes only job_name, as intended.


203-247: LGTM: edge cases are well covered.

Empty strings, None filtering, and single-field scenarios match AlertModel.orchestrator_info.


290-301: LGTM: model validation happy path.

Creation with optional icon validated correctly.


313-320: LGTM: required fields validation.

Exception expectations for missing fields are appropriate.

tests/unit/alerts/alert_messages/test_orchestrator_message_simple.py (3)

45-72: Relies on link text formatting — ensure upstream function formats display name.

This assertion depends on "View in Dbt Cloud" text. The upstream fix to create_orchestrator_link (title-casing) is required for this to pass consistently.


121-125: LGTM: covers GitHub Actions rendering path.

Validates both job info and link presence.


165-168: LGTM: full integration exercise.

End-to-end check that link URL is present in rendered output.

elementary/monitor/fetchers/alerts/schema/alert_data.py (4)

37-43: LGTM: orchestrator fields added to schema.

Optional fields with clear naming; safe defaults.


238-245: LGTM: TestAlertModel receives orchestrator fields.

Fields are forwarded correctly.


294-301: LGTM: ModelAlertModel receives orchestrator fields.

Consistent with TestAlert and SourceFreshness alert flows.


371-377: LGTM: SourceFreshnessAlertModel receives orchestrator fields.

End-to-end propagation covered.

elementary/monitor/alerts/test_alert.py (1)

51-84: AlertModel.init already defines job_id, job_name, job_run_id, job_url, job_run_url, and orchestrator; all super().init calls match this signature.

elementary/monitor/alerts/alert_messages/builder.py (2)

126-147: LGTM: inline “Job:” line with link is clear and compact.

Good use of BoldTextBlock + LinkInlineBlocks.


159-176: LGTM: avoids duplicate link when job line already embeds it.

Conditional merge into LinksLineBlock is correct.

elementary/monitor/alerts/model_alert.py (1)

40-46: Approve orchestrator/job context additions

All ModelAlertModel instantiations pass job_id, job_name, job_run_id, job_url, job_run_url, and orchestrator via keyword arguments; no positional usage detected.

Comment on lines 33 to 52
def test_create_orchestrator_link_formats_orchestrator_names(self):
test_cases = [
("airflow", "View in Airflow"),
("dbt_cloud", "View in Dbt Cloud"),
("github_actions", "View in Github Actions"),
("custom_orchestrator", "View in Custom Orchestrator"),
]

for orchestrator, expected_text in test_cases:
orchestrator_info = {
"orchestrator": orchestrator,
"run_url": "https://example.com/run/123", # noqa: E231
}

link = create_orchestrator_link(orchestrator_info)

assert link is not None
assert link.text == expected_text
assert link.orchestrator == orchestrator

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Align display text with function behavior (title-casing orchestrator).

The tests expect "View in Airflow/Dbt Cloud/Github Actions", but create_orchestrator_link currently uses the raw orchestrator string. Update the function to format display names (replace _ with space and title-case) to match these expectations.

Apply this change outside this file (in elementary/monitor/data_monitoring/alerts/integrations/utils/orchestrator_link.py):

 def create_orchestrator_link(
     orchestrator_info: Dict[str, str]
 ) -> Optional[OrchestratorLinkData]:
     """Create an orchestrator link from orchestrator info if URL is available."""
     if not orchestrator_info or not orchestrator_info.get("run_url"):
         return None

-    orchestrator = orchestrator_info.get("orchestrator", "orchestrator")
+    orchestrator = orchestrator_info.get("orchestrator", "orchestrator")
+    display_name = orchestrator.replace("_", " ").title()

     return OrchestratorLinkData(
         url=orchestrator_info["run_url"],
-        text=f"View in {orchestrator}",
-        orchestrator=orchestrator,
+        text=f"View in {display_name}",
+        orchestrator=orchestrator,
         icon=Icon.LINK,
     )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def test_create_orchestrator_link_formats_orchestrator_names(self):
test_cases = [
("airflow", "View in Airflow"),
("dbt_cloud", "View in Dbt Cloud"),
("github_actions", "View in Github Actions"),
("custom_orchestrator", "View in Custom Orchestrator"),
]
for orchestrator, expected_text in test_cases:
orchestrator_info = {
"orchestrator": orchestrator,
"run_url": "https://example.com/run/123", # noqa: E231
}
link = create_orchestrator_link(orchestrator_info)
assert link is not None
assert link.text == expected_text
assert link.orchestrator == orchestrator
def create_orchestrator_link(
orchestrator_info: Dict[str, str]
) -> Optional[OrchestratorLinkData]:
"""Create an orchestrator link from orchestrator info if URL is available."""
if not orchestrator_info or not orchestrator_info.get("run_url"):
return None
orchestrator = orchestrator_info.get("orchestrator", "orchestrator")
display_name = orchestrator.replace("_", " ").title()
return OrchestratorLinkData(
url=orchestrator_info["run_url"],
text=f"View in {display_name}",
orchestrator=orchestrator,
icon=Icon.LINK,
)
🤖 Prompt for AI Agents
In tests/unit/alerts/test_orchestrator_integration.py around lines 33 to 52 the
tests expect orchestrator display names like "View in Airflow" (title-cased,
with underscores replaced by spaces); update the implementation in
elementary/monitor/data_monitoring/alerts/integrations/utils/orchestrator_link.py
to format the orchestrator name by replacing "_" with " " and applying
title-case before composing the link text (e.g., text = f"View in
{formatted_orchestrator}"), while preserving the original orchestrator value in
the link.orchestrator and keeping run_url handling unchanged.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (1)
tests/unit/alerts/test_orchestrator_integration.py (1)

27-31: Resolved: icon expectation matches implementation (LINK).

The earlier comment about expecting INFO is now fixed; asserting Icon.LINK is correct.

🧹 Nitpick comments (2)
tests/unit/alerts/test_orchestrator_integration.py (2)

17-31: Lock in display text expectations for create_orchestrator_link (casing).

Current implementation uses raw orchestrator in text ("View in airflow"), while job links title-case ("Airflow"). Decide on consistent UX and assert it in tests.

  • If adopting title-case for run links too, update implementation (outside this file) and add this assertion:
         link = create_orchestrator_link(orchestrator_info)

         assert link is not None
         assert link.url == "https://airflow.example.com/run/12345"
         assert link.orchestrator == "airflow"
         assert link.icon == Icon.LINK
+        assert link.text == "View in Airflow"
  • Otherwise, if keeping raw casing, add:
+        assert link.text == "View in airflow"

46-60: Broaden coverage via parametrization for multiple orchestrators.

Parametrize this test over ["airflow", "dbt_cloud", "github_actions"] and assert the display text formatting (underscores → spaces, title-case) and icon GEAR consistently.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 476fae6 and 8244b2a.

📒 Files selected for processing (3)
  • elementary/monitor/alerts/alert.py (3 hunks)
  • tests/unit/alerts/alert_messages/test_orchestrator_message_simple.py (1 hunks)
  • tests/unit/alerts/test_orchestrator_integration.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • elementary/monitor/alerts/alert.py
  • tests/unit/alerts/alert_messages/test_orchestrator_message_simple.py
🧰 Additional context used
🧬 Code graph analysis (1)
tests/unit/alerts/test_orchestrator_integration.py (4)
elementary/messages/blocks.py (1)
  • Icon (8-23)
elementary/monitor/alerts/test_alert.py (1)
  • TestAlertModel (13-201)
elementary/monitor/data_monitoring/alerts/integrations/utils/orchestrator_link.py (3)
  • OrchestratorLinkData (7-11)
  • create_job_link (31-49)
  • create_orchestrator_link (14-28)
elementary/monitor/alerts/alert.py (1)
  • orchestrator_info (101-128)
🔇 Additional comments (2)
tests/unit/alerts/test_orchestrator_integration.py (2)

283-288: Syntax error: missing comma after keyword arg.

Add a trailing comma after orchestrator to make the call valid.

-            orchestrator="airflow"
+            orchestrator="airflow",

Likely an incorrect or invalid review comment.


112-114: Syntax error: missing comma after keyword arg.

Add a trailing comma after job_name to make the call valid.

-            job_name="test_job"
+            job_name="test_job",

Likely an incorrect or invalid review comment.

Comment on lines 1 to 4
import pytest

from elementary.messages.blocks import Icon
from elementary.monitor.alerts.model_alert import ModelAlertModel
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Import ValidationError for pydantic model assertions.

Tests rely on pydantic’s ValidationError; import it explicitly.

 import pytest
+
+from pydantic import ValidationError
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import pytest
from elementary.messages.blocks import Icon
from elementary.monitor.alerts.model_alert import ModelAlertModel
import pytest
from pydantic import ValidationError
from elementary.messages.blocks import Icon
from elementary.monitor.alerts.model_alert import ModelAlertModel
🤖 Prompt for AI Agents
In tests/unit/alerts/test_orchestrator_integration.py around lines 1 to 4, the
tests assert pydantic model validation failures but do not import
ValidationError; add "from pydantic import ValidationError" to the imports at
the top of the file so tests can reference ValidationError in their assertions.

Comment on lines 138 to 159
def test_model_alert_orchestrator_info(self):
alert = ModelAlertModel(
id="model_alert_id",
alias="test_model",
path="/models/test_model.sql",
original_path="models/test_model.sql",
materialization="table",
full_refresh=False,
alert_class_id="model_alert_class",
job_name="nightly_build",
job_run_id="67890",
orchestrator="dbt_cloud",
job_run_url="https://cloud.getdbt.com/run/67890",
)

info = alert.orchestrator_info
assert info is not None
assert info["job_name"] == "nightly_build"
assert info["run_id"] == "67890"
assert info["orchestrator"] == "dbt_cloud"
assert info["run_url"] == "https://cloud.getdbt.com/run/67890"

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Edge case not covered: only job_id present returns None due to gating.

AlertModel.orchestrator_info currently gates on job_name, job_run_id, orchestrator, job_url, job_run_url but not job_id. If only job_id is set, info incorrectly returns None. Consider including job_id in the gate or compute info first and return None if empty. I can open a PR to adjust base logic and add a unit test here.

Proposed fix outside this file (elementary/monitor/alerts/alert.py):

-        if not any(
-            [
-                self.job_name,
-                self.job_run_id,
-                self.orchestrator,
-                self.job_url,
-                self.job_run_url,
-            ]
-        ):
+        if not any(
+            [
+                self.job_id,
+                self.job_name,
+                self.job_run_id,
+                self.orchestrator,
+                self.job_url,
+                self.job_run_url,
+            ]
+        ):
             return None

🏁 Script executed:

#!/usr/bin/env bash
set -e

# Locate the orchestrator_info method
rg -n "def orchestrator_info" -t py -C5

# Locate the ModelAlertModel class
rg -n "class ModelAlertModel" -t py -C3

Length of output: 1278


🏁 Script executed:

#!/usr/bin/env bash
set -e

# Display orchestrator_info implementation
sed -n '90,150p' elementary/monitor/alerts/alert.py

Length of output: 1151


Add job_id to orchestrator_info gate and cover it with a test

  • In AlertModel.orchestrator_info, include self.job_id in the leading any([...]) check so that when only job_id is set, the method returns {"job_id": …} instead of None.
  • Add a unit test in tests/unit/alerts/test_orchestrator_integration.py verifying that an alert with only job_id yields orchestrator_info == {"job_id": "<value>"}.
🤖 Prompt for AI Agents
In tests/unit/alerts/test_orchestrator_integration.py around lines 138-159, the
review requests adding job_id to the orchestrator_info gate and covering it with
a unit test; update the AlertModel.orchestrator_info implementation to include
self.job_id in the leading any([...]) check so that when only job_id is present
the method returns {"job_id": self.job_id}; then add a new unit test in this
file that constructs a ModelAlertModel with only job_id set and asserts
alert.orchestrator_info == {"job_id": "<value>"}.

@arbiv
Copy link
Contributor

arbiv commented Nov 11, 2025

@quickcoffee can you enable "Allow edits by maintainers" on the PR?

@quickcoffee
Copy link
Contributor Author

@quickcoffee can you enable "Allow edits by maintainers" on the PR?

@arbiv done, though was already ticked as far as I can see 🤔
image

@arbiv arbiv had a problem deploying to elementary_test_env November 12, 2025 09:02 — with GitHub Actions Failure
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
elementary/monitor/alerts/alert_messages/builder.py (1)

123-179: Orchestrator link rendering logic is correct and handles edge cases well.

The implementation correctly handles multiple scenarios:

  • Inline job link when job_name and run_url are present
  • Fallback to summary text when job_name exists but run_url is missing
  • Separate links section when only run_url is available (no job_name)
  • Graceful handling when neither is present

The logic is sound and mutually exclusive branches prevent duplication.

Optional refactor: Extract job line construction for clarity.

The nested conditionals (lines 127-150) could be extracted into a helper method to improve readability. For example:

def _create_job_line(self, orchestrator_info: Dict[str, str]) -> Optional[LineBlock]:
    """Create inline job line with orchestrator link if available."""
    orchestrator_name = orchestrator_info.get("orchestrator", "orchestrator")
    job_info_text = f"{orchestrator_info['job_name']} (via {orchestrator_name})"
    
    orchestrator_link = create_orchestrator_link(orchestrator_info)
    if not orchestrator_link:
        return None
    
    job_inlines: list[InlineBlock] = [
        BoldTextBlock(text="Job:"),
        TextBlock(text=job_info_text + " | "),
    ]
    job_inlines.extend(
        LinkInlineBlocks(
            text=orchestrator_link.text,
            url=orchestrator_link.url,
            icon=orchestrator_link.icon,
        )
    )
    return LineBlock(inlines=job_inlines)

Then simplify the main logic:

if orchestrator_info and orchestrator_info.get("job_name"):
    job_line = self._create_job_line(orchestrator_info)
    if job_line:
        subtitle_lines.append(job_line)
    else:
        job_info_text = f"{orchestrator_info['job_name']} (via {orchestrator_info.get('orchestrator', 'orchestrator')})"
        summary.append(("Job:", job_info_text))
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8244b2a and 7f13126.

📒 Files selected for processing (1)
  • elementary/monitor/alerts/alert_messages/builder.py (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
elementary/monitor/alerts/alert_messages/builder.py (4)
elementary/messages/block_builders.py (4)
  • BoldTextBlock (63-64)
  • LinkInlineBlocks (85-92)
  • SummaryLineBlock (109-122)
  • LinksLineBlock (99-106)
elementary/monitor/data_monitoring/alerts/integrations/utils/orchestrator_link.py (1)
  • create_orchestrator_link (14-28)
elementary/monitor/alerts/alert.py (2)
  • orchestrator_info (101-128)
  • summary (94-95)
elementary/messages/blocks.py (2)
  • TextBlock (40-43)
  • LineBlock (67-70)
🔇 Additional comments (3)
elementary/monitor/alerts/alert_messages/builder.py (3)

7-7: LGTM! Import additions support orchestrator link rendering.

The new imports (BoldTextBlock, LinkInlineBlocks, and create_orchestrator_link) are all used in the subtitle rendering logic and come from appropriate modules.

Also applies to: 13-13, 47-49


114-114: LGTM! Parameter addition is backward compatible.

The orchestrator_info parameter is properly typed, has a sensible default, and maintains backward compatibility with existing callers.


206-206: LGTM! Clean orchestrator info propagation.

The orchestrator info is correctly retrieved from the alert's property (line 206) and cleanly passed through to the subtitle rendering (line 216). The types align properly throughout the call chain.

Also applies to: 216-216

@arbiv arbiv had a problem deploying to elementary_test_env December 17, 2025 12:20 — with GitHub Actions Failure
if detected_at_str:
summary.append(("Time:", detected_at_str))

# Initialize subtitle lines with summary
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please remove all of the inline comments you added, as it goes agains the conventions of the project (only complicated/unintuitive code should be commented)

Relevant to the entire PR

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I removed all of them now :)

raise NotImplementedError

@property
def orchestrator_info(self) -> Optional[Dict[str, str]]:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dont return a dict to represent structured data, create a dedicated OrchestratorInfo class for it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added OrchestratorInfo instead

@ofek1weiss
Copy link
Contributor

Hey @quickcoffee sorry for the delay on the review, it slipped a bit through the cracks

Overall looks awesome 🤘
The functionality looks great, left a few comments around styling and best practices.

@quickcoffee
Copy link
Contributor Author

@ofek1weiss thanks for the review! I made the changes you suggested :)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
elementary/monitor/data_monitoring/alerts/integrations/utils/orchestrator_link.py (1)

32-49: Consider extracting the display_name transformation to reduce duplication.

Line 42 duplicates logic from elementary/monitor/alerts/test_alert.py (lines 126-127), where a display_name function performs the same transformation. Consider extracting this to a shared utility module to maintain DRY principles.

♻️ Suggested refactor

Create a shared utility function (e.g., in elementary/utils/strings.py):

def format_display_name(value: str) -> str:
    """Convert snake_case to Title Case for display."""
    return value.replace("_", " ").title()

Then import and use it here:

+from elementary.utils.strings import format_display_name
+
 def create_job_link(
     orchestrator_info: OrchestratorInfo,
 ) -> Optional[OrchestratorLinkData]:
     """Create a job-level orchestrator link if job URL is available."""
     if not orchestrator_info or not orchestrator_info.job_url:
         return None
 
     orchestrator = orchestrator_info.orchestrator or "orchestrator"
     job_name = orchestrator_info.job_name or "Job"
 
-    display_name = orchestrator.replace("_", " ").title()
+    display_name = format_display_name(orchestrator)
 
     return OrchestratorLinkData(
         url=orchestrator_info.job_url,
         text=f"{job_name} in {display_name}",
         orchestrator=orchestrator,
         icon=Icon.GEAR,
     )
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 7f13126 and 77dbc41.

📒 Files selected for processing (5)
  • elementary/monitor/alerts/alert.py
  • elementary/monitor/alerts/alert_messages/builder.py
  • elementary/monitor/data_monitoring/alerts/integrations/utils/orchestrator_link.py
  • tests/unit/alerts/alert_messages/test_orchestrator_message_simple.py
  • tests/unit/alerts/test_orchestrator_integration.py
🧰 Additional context used
🧬 Code graph analysis (3)
tests/unit/alerts/alert_messages/test_orchestrator_message_simple.py (4)
elementary/messages/formats/block_kit.py (1)
  • format_block_kit (360-364)
elementary/monitor/alerts/model_alert.py (2)
  • ModelAlertModel (14-116)
  • get_report_link (115-116)
tests/unit/alerts/alert_messages/test_alert_utils.py (1)
  • get_alert_message_body (305-316)
elementary/monitor/alerts/alert.py (2)
  • get_report_link (109-110)
  • orchestrator_info (113-133)
tests/unit/alerts/test_orchestrator_integration.py (2)
elementary/monitor/alerts/alert.py (2)
  • OrchestratorInfo (16-24)
  • orchestrator_info (113-133)
elementary/monitor/data_monitoring/alerts/integrations/utils/orchestrator_link.py (3)
  • OrchestratorLinkData (8-12)
  • create_job_link (32-49)
  • create_orchestrator_link (15-29)
elementary/monitor/data_monitoring/alerts/integrations/utils/orchestrator_link.py (3)
elementary/messages/blocks.py (1)
  • Icon (8-23)
elementary/monitor/alerts/alert.py (2)
  • OrchestratorInfo (16-24)
  • orchestrator_info (113-133)
elementary/monitor/alerts/test_alert.py (1)
  • display_name (127-128)
🔇 Additional comments (5)
elementary/monitor/alerts/alert.py (2)

16-24: LGTM! Clean data model for orchestrator metadata.

The OrchestratorInfo model is well-structured with all fields appropriately optional, enabling flexible orchestrator integration across different alert types.


112-133: LGTM! Orchestrator info property logic is correct.

The gating logic correctly returns None when no meaningful orchestrator data is present. The exclusion of job_id from the gating check (lines 115-123) is appropriate, as job_id alone is insufficient context. The or None normalization (line 127) properly handles empty strings.

tests/unit/alerts/alert_messages/test_orchestrator_message_simple.py (1)

1-165: LGTM! Comprehensive test coverage for orchestrator message rendering.

The tests effectively cover:

  • Orchestrator data inclusion in messages
  • Link rendering with URLs
  • Backward compatibility (alerts without orchestrator data)
  • Multiple alert types (TestAlertModel, ModelAlertModel)
  • Property validation and integration

The use of str() conversion for text search (line 39) is pragmatic for integration-style testing.

tests/unit/alerts/test_orchestrator_integration.py (1)

1-302: LGTM! Excellent comprehensive test coverage.

The test suite thoroughly validates:

  • Link creation with various data scenarios (valid, missing URL, empty info)
  • orchestrator_info property behavior across all alert model types
  • Edge cases including empty strings, None values, and partial data
  • Model validation for required fields

The structured approach with dedicated test classes makes the coverage easy to verify and maintain.

elementary/monitor/alerts/alert_messages/builder.py (1)

106-172: LGTM! The orchestrator rendering logic is well-designed.

The conditional rendering strategy is sound:

  • When job_name is present: renders job info inline with optional orchestrator link
  • When job_name is absent: adds orchestrator link to the links section

The fallback values (line 128: orchestrator_name = orchestrator_info.orchestrator or "orchestrator") ensure graceful degradation, and the link aggregation logic (lines 153-170) properly handles both existing and new orchestrator links.

@ofek1weiss
Copy link
Contributor

@quickcoffee approved your changes, but looks like some of the tests are failing
We have some tests to verify that the alerts look a certain way, and now that you changed how they look, the tests fail...

If you can, run the tests with the following env variable: OVERRIDE=true to override the expected results, and push the changes.

I can help with doing that if this does not work for you 👍

@quickcoffee
Copy link
Contributor Author

quickcoffee commented Jan 11, 2026

If you can, run the tests with the following env variable: OVERRIDE=true to override the expected results, and push the changes.
I can help with doing that if this does not work for you 👍

@ofek1weiss could you point me into the right direction for this? I tried OVERRIDE=true pytest but that didn't result in any changed files I could push. Afterwards I did OVERRIDE=true py.test -vvv from ./test and pushed the all_icons.* files - was that it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants