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
4 changes: 3 additions & 1 deletion sqlmesh/core/console.py
Original file line number Diff line number Diff line change
Expand Up @@ -3107,7 +3107,9 @@ class MarkdownConsole(CaptureTerminalConsole):
AUDIT_PADDING = 7

def __init__(self, **kwargs: t.Any) -> None:
super().__init__(**{**kwargs, "console": RichConsole(no_color=True)})
super().__init__(
**{**kwargs, "console": RichConsole(no_color=True, width=kwargs.pop("width", None))}
)

def show_environment_difference_summary(
self,
Expand Down
4 changes: 3 additions & 1 deletion sqlmesh/integrations/github/cicd/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@
@click.pass_context
def github(ctx: click.Context, token: str) -> None:
"""Github Action CI/CD Bot. See https://sqlmesh.readthedocs.io/en/stable/integrations/github/ for details"""
set_console(MarkdownConsole())
# set a larger width because if none is specified, it auto-detects 80 characters when running in GitHub Actions
# which can result in surprise newlines when outputting dates to backfill
set_console(MarkdownConsole(width=1000))
ctx.obj["github"] = GithubController(
paths=ctx.obj["paths"],
token=token,
Expand Down
9 changes: 9 additions & 0 deletions sqlmesh/integrations/github/cicd/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,12 @@ def _append_output(cls, key: str, value: str) -> None:
print(f"{key}={value}", file=fh)

def get_plan_summary(self, plan: Plan) -> str:
# use Verbosity.VERY_VERBOSE to prevent the list of models from being truncated
# this is particularly important for the "Models needing backfill" list because
# there is no easy way to tell this otherwise
orig_verbosity = self._console.verbosity
self._console.verbosity = Verbosity.VERY_VERBOSE

try:
# Clear out any output that might exist from prior steps
self._console.clear_captured_outputs()
Expand Down Expand Up @@ -517,7 +523,10 @@ def get_plan_summary(self, plan: Plan) -> str:

return f"{difference_summary}\n{missing_dates}{plan_flags_section}"
except PlanError as e:
logger.exception("Plan failed to generate")
return f"Plan failed to generate. Check for pending or unresolved changes. Error: {e}"
finally:
self._console.verbosity = orig_verbosity

def run_tests(self) -> t.Tuple[ModelTextTestResult, str]:
"""
Expand Down
38 changes: 38 additions & 0 deletions tests/integrations/github/cicd/test_github_controller.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# type: ignore
import typing as t
import os
import pathlib
from unittest import mock
Expand All @@ -17,6 +18,7 @@
BotCommand,
MergeStateStatus,
)
from sqlmesh.integrations.github.cicd.controller import GithubController
from sqlmesh.integrations.github.cicd.command import _update_pr_environment
from sqlmesh.utils.date import to_datetime, now
from tests.integrations.github.cicd.conftest import MockIssueComment
Expand Down Expand Up @@ -591,3 +593,39 @@ def test_uncategorized(
assert "The following models could not be categorized automatically" in summary
assert '- "b"' in summary
assert "Run `sqlmesh plan hello_world_2` locally to apply these changes" in summary


def test_get_plan_summary_doesnt_truncate_backfill_list(
github_client, make_controller: t.Callable[..., GithubController]
):
controller = make_controller(
"tests/fixtures/github/pull_request_synchronized.json",
github_client,
mock_out_context=False,
)

summary = controller.get_plan_summary(controller.prod_plan)

assert "more ...." not in summary

assert (
"""**Models needing backfill:**
* `memory.raw.demographics`: [full refresh]
* `memory.sushi.active_customers`: [full refresh]
* `memory.sushi.count_customers_active`: [full refresh]
* `memory.sushi.count_customers_inactive`: [full refresh]
* `memory.sushi.customer_revenue_by_day`: [2025-06-30 - 2025-07-06]
* `memory.sushi.customer_revenue_lifetime`: [2025-06-30 - 2025-07-06]
* `memory.sushi.customers`: [full refresh]
* `memory.sushi.items`: [2025-06-30 - 2025-07-06]
* `memory.sushi.latest_order`: [full refresh]
* `memory.sushi.marketing`: [2025-06-30 - 2025-07-06]
* `memory.sushi.order_items`: [2025-06-30 - 2025-07-06]
* `memory.sushi.orders`: [2025-06-30 - 2025-07-06]
* `memory.sushi.raw_marketing`: [full refresh]
* `memory.sushi.top_waiters`: [recreate view]
* `memory.sushi.waiter_as_customer_by_day`: [2025-06-30 - 2025-07-06]
* `memory.sushi.waiter_names`: [full refresh]
* `memory.sushi.waiter_revenue_by_day`: [2025-06-30 - 2025-07-06]"""
in summary
)
62 changes: 31 additions & 31 deletions tests/integrations/github/cicd/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ def test_merge_pr_has_non_breaking_change(
assert GithubCheckConclusion(prod_plan_preview_checks_runs[2]["conclusion"]).is_success

expected_prod_plan_directly_modified_summary = """**Directly Modified:**
* `sushi.waiter_revenue_by_day` (Non-breaking)
* `memory.sushi.waiter_revenue_by_day` (Non-breaking)
Copy link
Collaborator Author

@erindru erindru Jul 7, 2025

Choose a reason for hiding this comment

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

This is a side effect of setting Verbosity.VERY_VERBOSE.

However, it does have the effect of making the output of this step more closely align with the PR Environment Synced step which is using fully qualified names already


```diff
---
Expand All @@ -318,10 +318,10 @@ def test_merge_pr_has_non_breaking_change(
ON o.id = oi.order_id AND o.event_date = oi.event_date
```
Indirectly Modified Children:
- `sushi.top_waiters` (Indirect Non-breaking)
- `memory.sushi.top_waiters` (Indirect Non-breaking)
"""
expected_prod_plan_indirectly_modified_summary = """**Indirectly Modified:**
- `sushi.top_waiters` (Indirect Non-breaking)
- `memory.sushi.top_waiters` (Indirect Non-breaking)
"""

assert prod_plan_preview_checks_runs[2]["output"]["title"] == "Prod Plan Preview"
Expand Down Expand Up @@ -509,7 +509,7 @@ def test_merge_pr_has_non_breaking_change_diff_start(
assert prod_plan_preview_checks_runs[2]["output"]["title"] == "Prod Plan Preview"

expected_prod_plan_directly_modified_summary = """**Directly Modified:**
* `sushi.waiter_revenue_by_day` (Non-breaking)
* `memory.sushi.waiter_revenue_by_day` (Non-breaking)

```diff
---
Expand All @@ -529,10 +529,10 @@ def test_merge_pr_has_non_breaking_change_diff_start(
ON o.id = oi.order_id AND o.event_date = oi.event_date
```
Indirectly Modified Children:
- `sushi.top_waiters` (Indirect Non-breaking)
- `memory.sushi.top_waiters` (Indirect Non-breaking)
"""
expected_prod_plan_indirectly_modified_summary = """**Indirectly Modified:**
- `sushi.top_waiters` (Indirect Non-breaking)
- `memory.sushi.top_waiters` (Indirect Non-breaking)
"""

prod_plan_preview_summary = prod_plan_preview_checks_runs[2]["output"]["summary"]
Expand Down Expand Up @@ -1032,7 +1032,7 @@ def test_no_merge_since_no_deploy_signal(
assert GithubCheckConclusion(prod_plan_preview_checks_runs[2]["conclusion"]).is_success

expected_prod_plan_directly_modified_summary = """**Directly Modified:**
* `sushi.waiter_revenue_by_day` (Non-breaking)
* `memory.sushi.waiter_revenue_by_day` (Non-breaking)

```diff
---
Expand All @@ -1052,10 +1052,10 @@ def test_no_merge_since_no_deploy_signal(
ON o.id = oi.order_id AND o.event_date = oi.event_date
```
Indirectly Modified Children:
- `sushi.top_waiters` (Indirect Non-breaking)"""
- `memory.sushi.top_waiters` (Indirect Non-breaking)"""

expected_prod_plan_indirectly_modified_summary = """**Indirectly Modified:**
- `sushi.top_waiters` (Indirect Non-breaking)
- `memory.sushi.top_waiters` (Indirect Non-breaking)
"""

assert prod_plan_preview_checks_runs[2]["output"]["title"] == "Prod Plan Preview"
Expand Down Expand Up @@ -1232,7 +1232,7 @@ def test_no_merge_since_no_deploy_signal_no_approvers_defined(
assert GithubCheckStatus(prod_plan_preview_checks_runs[2]["status"]).is_completed
assert GithubCheckConclusion(prod_plan_preview_checks_runs[2]["conclusion"]).is_success
expected_prod_plan_directly_modified_summary = """**Directly Modified:**
* `sushi.waiter_revenue_by_day` (Non-breaking)
* `memory.sushi.waiter_revenue_by_day` (Non-breaking)

```diff
---
Expand All @@ -1252,10 +1252,10 @@ def test_no_merge_since_no_deploy_signal_no_approvers_defined(
ON o.id = oi.order_id AND o.event_date = oi.event_date
```
Indirectly Modified Children:
- `sushi.top_waiters` (Indirect Non-breaking)
- `memory.sushi.top_waiters` (Indirect Non-breaking)
"""
expected_prod_plan_indirectly_modified_summary = """**Indirectly Modified:**
- `sushi.top_waiters` (Indirect Non-breaking)
- `memory.sushi.top_waiters` (Indirect Non-breaking)
"""
assert prod_plan_preview_checks_runs[2]["output"]["title"] == "Prod Plan Preview"
prod_plan_preview_summary = prod_plan_preview_checks_runs[2]["output"]["summary"]
Expand Down Expand Up @@ -1414,7 +1414,7 @@ def test_deploy_comment_pre_categorized(
assert GithubCheckStatus(prod_plan_preview_checks_runs[2]["status"]).is_completed
assert GithubCheckConclusion(prod_plan_preview_checks_runs[2]["conclusion"]).is_success
expected_prod_plan_directly_modified_summary = """**Directly Modified:**
* `sushi.waiter_revenue_by_day` (Non-breaking)
* `memory.sushi.waiter_revenue_by_day` (Non-breaking)

```diff
---
Expand All @@ -1434,10 +1434,10 @@ def test_deploy_comment_pre_categorized(
ON o.id = oi.order_id AND o.event_date = oi.event_date
```
Indirectly Modified Children:
- `sushi.top_waiters` (Indirect Non-breaking)
- `memory.sushi.top_waiters` (Indirect Non-breaking)
"""
expected_prod_plan_indirectly_modified_summary = """**Indirectly Modified:**
- `sushi.top_waiters` (Indirect Non-breaking)
- `memory.sushi.top_waiters` (Indirect Non-breaking)
"""
assert prod_plan_preview_checks_runs[2]["output"]["title"] == "Prod Plan Preview"
prod_plan_preview_summary = prod_plan_preview_checks_runs[2]["output"]["summary"]
Expand Down Expand Up @@ -1781,7 +1781,7 @@ def test_overlapping_changes_models(
assert GithubCheckConclusion(prod_plan_preview_checks_runs[2]["conclusion"]).is_success

expected_prod_plan_directly_modified_summary = """**Directly Modified:**
* `sushi.customers` (Non-breaking)
* `memory.sushi.customers` (Non-breaking)

```diff
---
Expand All @@ -1801,23 +1801,23 @@ def test_overlapping_changes_models(
WITH current_marketing AS (
```
Indirectly Modified Children:
- `sushi.active_customers` (Indirect Non-breaking)
- `sushi.count_customers_active` (Indirect Non-breaking)
- `sushi.count_customers_inactive` (Indirect Non-breaking)
- `sushi.waiter_as_customer_by_day` (Indirect Breaking)
- `memory.sushi.active_customers` (Indirect Non-breaking)
- `memory.sushi.count_customers_active` (Indirect Non-breaking)
- `memory.sushi.count_customers_inactive` (Indirect Non-breaking)
- `memory.sushi.waiter_as_customer_by_day` (Indirect Breaking)


* `sushi.waiter_names` (Breaking)
* `memory.sushi.waiter_names` (Breaking)


Indirectly Modified Children:
- `sushi.waiter_as_customer_by_day` (Indirect Breaking)"""
- `memory.sushi.waiter_as_customer_by_day` (Indirect Breaking)"""

expected_prod_plan_indirectly_modified_summary = """**Indirectly Modified:**
- `sushi.active_customers` (Indirect Non-breaking)
- `sushi.count_customers_active` (Indirect Non-breaking)
- `sushi.count_customers_inactive` (Indirect Non-breaking)
- `sushi.waiter_as_customer_by_day` (Indirect Breaking)"""
- `memory.sushi.active_customers` (Indirect Non-breaking)
- `memory.sushi.count_customers_active` (Indirect Non-breaking)
- `memory.sushi.count_customers_inactive` (Indirect Non-breaking)
- `memory.sushi.waiter_as_customer_by_day` (Indirect Breaking)"""

assert prod_plan_preview_checks_runs[2]["output"]["title"] == "Prod Plan Preview"
prod_plan_preview_summary = prod_plan_preview_checks_runs[2]["output"]["summary"]
Expand Down Expand Up @@ -1993,7 +1993,7 @@ def test_pr_add_model(
)

expected_prod_plan_summary = """**Added Models:**
- `sushi.cicd_test_model` (Breaking)"""
- `memory.sushi.cicd_test_model` (Breaking)"""

assert "SQLMesh - Prod Plan Preview" in controller._check_run_mapping
prod_plan_preview_checks_runs = controller._check_run_mapping[
Expand Down Expand Up @@ -2144,7 +2144,7 @@ def test_pr_delete_model(
)

expected_prod_plan_summary = """**Removed Models:**
- `sushi.top_waiters` (Breaking)"""
- `memory.sushi.top_waiters` (Breaking)"""

assert "SQLMesh - Prod Plan Preview" in controller._check_run_mapping
prod_plan_preview_checks_runs = controller._check_run_mapping[
Expand Down Expand Up @@ -2330,7 +2330,7 @@ def test_has_required_approval_but_not_base_branch(
assert GithubCheckStatus(prod_plan_preview_checks_runs[2]["status"]).is_completed
assert GithubCheckConclusion(prod_plan_preview_checks_runs[2]["conclusion"]).is_success
expected_prod_plan_directly_modified_summary = """**Directly Modified:**
* `sushi.waiter_revenue_by_day` (Non-breaking)
* `memory.sushi.waiter_revenue_by_day` (Non-breaking)

```diff
---
Expand All @@ -2350,10 +2350,10 @@ def test_has_required_approval_but_not_base_branch(
ON o.id = oi.order_id AND o.event_date = oi.event_date
```
Indirectly Modified Children:
- `sushi.top_waiters` (Indirect Non-breaking)"""
- `memory.sushi.top_waiters` (Indirect Non-breaking)"""

expected_prod_plan_indirectly_modified_summary = """**Indirectly Modified:**
- `sushi.top_waiters` (Indirect Non-breaking)"""
- `memory.sushi.top_waiters` (Indirect Non-breaking)"""

assert prod_plan_preview_checks_runs[2]["output"]["title"] == "Prod Plan Preview"
prod_plan_preview_summary = prod_plan_preview_checks_runs[2]["output"]["summary"]
Expand Down