Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 importanmt for the "Models needing backfill" list because
Comment thread
erindru marked this conversation as resolved.
Outdated
# 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
Copy Markdown
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