Skip to content

Commit 687ab73

Browse files
authored
fix: github cicd support uncategorized snapshots (#1678)
* fix: github cicd skip downstream uncategorized snapshots * fix: github cicd support uncategorized snapshots
1 parent 911c745 commit 687ab73

File tree

3 files changed

+97
-16
lines changed

3 files changed

+97
-16
lines changed

sqlmesh/core/plan/definition.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -272,19 +272,32 @@ def restatements(self) -> t.Dict[str, Interval]:
272272
return self._restatements
273273

274274
@property
275-
def loaded_snapshot_intervals(self) -> t.List[LoadedSnapshotIntervals]:
275+
def loaded_snapshot_intervals(
276+
self,
277+
) -> t.Tuple[t.List[LoadedSnapshotIntervals], t.List[Snapshot]]:
276278
loaded_snapshots = []
279+
unloaded_snapshots = []
277280
for snapshot in self.directly_modified:
278-
if not snapshot.change_category:
279-
continue
280-
loaded_snapshots.append(LoadedSnapshotIntervals.from_snapshot(snapshot))
281+
if snapshot.change_category:
282+
loaded_snapshots.append(LoadedSnapshotIntervals.from_snapshot(snapshot))
283+
else:
284+
logger.debug(f"Got an unloaded snapshot. Snapshot: {snapshot.name}")
285+
unloaded_snapshots.append(snapshot)
281286
for downstream_indirect in self.indirectly_modified.get(snapshot.name, set()):
282287
downstream_snapshot = self._snapshot_mapping[downstream_indirect]
283288
# We don't want to display indirect non-breaking since to users these are effectively no-op changes
284289
if downstream_snapshot.is_indirect_non_breaking:
285290
continue
286-
loaded_snapshots.append(LoadedSnapshotIntervals.from_snapshot(downstream_snapshot))
287-
return loaded_snapshots
291+
if downstream_snapshot.change_category:
292+
loaded_snapshots.append(
293+
LoadedSnapshotIntervals.from_snapshot(downstream_snapshot)
294+
)
295+
else:
296+
logger.debug(
297+
f"Got an unloaded indirectly-modified snapshot. Snapshot: {downstream_snapshot.name}"
298+
)
299+
unloaded_snapshots.append(downstream_snapshot)
300+
return loaded_snapshots, unloaded_snapshots
288301

289302
@property
290303
def has_changes(self) -> bool:

sqlmesh/integrations/github/cicd/controller.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from sqlmesh.core.context import Context
2323
from sqlmesh.core.environment import Environment
2424
from sqlmesh.core.plan import LoadedSnapshotIntervals, Plan
25+
from sqlmesh.core.snapshot.definition import Snapshot
2526
from sqlmesh.core.user import User
2627
from sqlmesh.integrations.github.cicd.config import GithubCICDBotConfig
2728
from sqlmesh.utils.concurrency import NodeExecutionFailedError
@@ -547,7 +548,9 @@ def try_invalidate_pr_environment(self) -> None:
547548
if self.bot_config.invalidate_environment_after_deploy:
548549
self._context.invalidate_environment(self.pr_environment_name)
549550

550-
def get_loaded_snapshot_intervals(self) -> t.List[LoadedSnapshotIntervals]:
551+
def get_loaded_snapshot_intervals(
552+
self,
553+
) -> t.Tuple[t.List[LoadedSnapshotIntervals], t.List[Snapshot]]:
551554
return self.prod_plan_with_gaps.loaded_snapshot_intervals
552555

553556
def _update_check(
@@ -729,8 +732,8 @@ def conclusion_handler(
729732
conclusion: GithubCheckConclusion, exception: t.Optional[Exception]
730733
) -> t.Tuple[GithubCheckConclusion, str, t.Optional[str]]:
731734
if conclusion.is_success:
732-
pr_affected_models = self.get_loaded_snapshot_intervals()
733-
if not pr_affected_models:
735+
loaded_snapshots, unloaded_snapshots = self.get_loaded_snapshot_intervals()
736+
if not loaded_snapshots and not unloaded_snapshots:
734737
summary = "No models were modified in this PR.\n"
735738
else:
736739
header_rows = [
@@ -742,16 +745,24 @@ def conclusion_handler(
742745
],
743746
]
744747
body_rows: List[Element | List[Element]] = []
745-
for affected_model in pr_affected_models:
748+
for loaded_snapshot in loaded_snapshots:
746749
model_rows = [
747-
h("td", affected_model.node_name),
748-
h("td", affected_model.change_category_str),
750+
h("td", loaded_snapshot.node_name),
751+
h("td", loaded_snapshot.change_category_str),
749752
]
750-
if affected_model.intervals:
751-
model_rows.append(h("td", affected_model.format_intervals()))
753+
if loaded_snapshot.intervals:
754+
model_rows.append(h("td", loaded_snapshot.format_intervals()))
752755
else:
753756
model_rows.append(h("td", "N/A"))
754757
body_rows.append(model_rows)
758+
for unloaded_snapshot in unloaded_snapshots:
759+
body_rows.append(
760+
[
761+
h("td", unloaded_snapshot.name),
762+
h("td", "Uncategorized"),
763+
h("td", "N/A"),
764+
]
765+
)
755766
table_header = h("thead", [h("tr", row) for row in header_rows])
756767
table_body = h("tbody", [h("tr", row) for row in body_rows])
757768
summary = str(h("table", [table_header, table_body]))

tests/integrations/github/cicd/test_github_controller.py

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,22 @@
11
# type: ignore
2-
from unittest.mock import call
2+
from unittest.mock import PropertyMock, call
33

44
import pytest
55
from pytest_mock.plugin import MockerFixture
66

77
from sqlmesh.core import constants as c
88
from sqlmesh.core.config import CategorizerConfig
9+
from sqlmesh.core.dialect import parse_one
10+
from sqlmesh.core.model import SqlModel
11+
from sqlmesh.core.plan import LoadedSnapshotIntervals
12+
from sqlmesh.core.snapshot import SnapshotChangeCategory
913
from sqlmesh.core.user import User, UserRole
1014
from sqlmesh.integrations.github.cicd.config import GithubCICDBotConfig, MergeMethod
11-
from sqlmesh.integrations.github.cicd.controller import BotCommand, MergeStateStatus
15+
from sqlmesh.integrations.github.cicd.controller import (
16+
BotCommand,
17+
GithubCheckStatus,
18+
MergeStateStatus,
19+
)
1220
from tests.integrations.github.cicd.fixtures import MockIssueComment
1321

1422
pytest_plugins = ["tests.integrations.github.cicd.fixtures"]
@@ -503,3 +511,52 @@ def test_bot_command_parsing(
503511
),
504512
)
505513
assert controller.get_command_from_comment() == command
514+
515+
516+
def test_unloaded_snapshots(
517+
mocker,
518+
github_client,
519+
make_controller,
520+
make_snapshot,
521+
make_mock_check_run,
522+
make_mock_issue_comment,
523+
):
524+
snapshot_categrozied = make_snapshot(SqlModel(name="a", query=parse_one("select 1, ds")))
525+
snapshot_categrozied.categorize_as(SnapshotChangeCategory.BREAKING)
526+
snapshot_uncategorized = make_snapshot(SqlModel(name="b", query=parse_one("select 1, ds")))
527+
mocker.patch(
528+
"sqlmesh.core.plan.Plan.loaded_snapshot_intervals",
529+
PropertyMock(
530+
return_value=(
531+
[LoadedSnapshotIntervals.from_snapshot(snapshot_categrozied)],
532+
[snapshot_uncategorized],
533+
)
534+
),
535+
)
536+
mock_repo = github_client.get_repo()
537+
mock_repo.create_check_run = mocker.MagicMock(
538+
side_effect=lambda **kwargs: make_mock_check_run(**kwargs)
539+
)
540+
541+
created_comments = []
542+
mock_issue = mock_repo.get_issue()
543+
mock_issue.create_comment = mocker.MagicMock(
544+
side_effect=lambda comment: make_mock_issue_comment(
545+
comment=comment, created_comments=created_comments
546+
)
547+
)
548+
mock_issue.get_comments = mocker.MagicMock(side_effect=lambda: created_comments)
549+
controller = make_controller(
550+
"tests/fixtures/github/pull_request_synchronized.json", github_client
551+
)
552+
controller.update_pr_environment_check(GithubCheckStatus.COMPLETED)
553+
554+
assert "SQLMesh - PR Environment Synced" in controller._check_run_mapping
555+
pr_environment_check_run = controller._check_run_mapping[
556+
"SQLMesh - PR Environment Synced"
557+
].all_kwargs
558+
assert len(pr_environment_check_run) == 1
559+
assert (
560+
pr_environment_check_run[0]["output"]["summary"]
561+
== """<table><thead><tr><th colspan="3">PR Environment Summary</th></tr><tr><th>Model</th><th>Change Type</th><th>Dates Loaded</th></tr></thead><tbody><tr><td>a</td><td>Breaking</td><td>N/A</td></tr><tr><td>b</td><td>Uncategorized</td><td>N/A</td></tr></tbody></table>"""
562+
)

0 commit comments

Comments
 (0)