Skip to content

Commit 8ebc2f1

Browse files
Align dcm test/refresh progress UI and unify the DCM blue
test and refresh now render the shared phase checklist (a single TEST / REFRESH phase driven by DeployProgressTracker.run_loader_phase) instead of the legacy generic spinner, matching plan/compile/deploy. - progress: add single-phase "test" and "refresh" operation modes and guard complete_upload() so operations without an UPLOAD phase don't KeyError when run_loader_phase calls it. - styles: collapse the two divergent blues (#a0a8fe and the named "blue") onto a single terminal-default BLUE constant, and route the hardcoded style="blue" literals in progress.py through it, so every blue in DCM output renders the same hue. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 5a9474d commit 8ebc2f1

4 files changed

Lines changed: 46 additions & 20 deletions

File tree

src/snowflake/cli/_plugins/dcm/commands.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -847,9 +847,13 @@ def refresh(
847847
context = _resolve_context_with_optional_manifest(from_location, identifier, target)
848848
project_id = context.project_identifier
849849

850-
with cli_console.spinner() as spinner:
851-
spinner.add_task(description=f"Refreshing dcm project {project_id}", total=None)
852-
result = DCMProjectManager().refresh(project_identifier=project_id)
850+
manager = DCMProjectManager()
851+
tracker = DeployProgressTracker(conn=manager.connection, operation="refresh")
852+
with tracker.session():
853+
result = tracker.run_loader_phase(
854+
lambda: manager.refresh(project_identifier=project_id),
855+
phase_name="REFRESH",
856+
)
853857

854858
reporter = RefreshReporter(save_output=save_output)
855859
return reporter.process(result)
@@ -872,9 +876,13 @@ def test(
872876
context = _resolve_context_with_optional_manifest(from_location, identifier, target)
873877
project_id = context.project_identifier
874878

875-
with cli_console.spinner() as spinner:
876-
spinner.add_task(description=f"Testing dcm project {project_id}", total=None)
877-
result = DCMProjectManager().test(project_identifier=project_id)
879+
manager = DCMProjectManager()
880+
tracker = DeployProgressTracker(conn=manager.connection, operation="test")
881+
with tracker.session():
882+
result = tracker.run_loader_phase(
883+
lambda: manager.test(project_identifier=project_id),
884+
phase_name="TEST",
885+
)
878886

879887
reporter = TestReporter(save_output=save_output)
880888
return reporter.process(result)

src/snowflake/cli/_plugins/dcm/progress.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,20 @@
4545
# after COMPILE (there are no server-side progress phases to poll, so RENDER is
4646
# fast-forwarded and COMPILE shows the live spinner while the server works).
4747
COMPILE_OPERATION_PHASES = ["RENDER", "COMPILE"]
48-
OperationMode = Literal["deploy", "plan", "compile", "purge"]
48+
OperationMode = Literal["deploy", "plan", "compile", "purge", "test", "refresh"]
4949

5050
# Server-side phase list per operation mode. UPLOAD is first for operations
51-
# that sync local files; purge runs entirely on the server (no local upload),
52-
# so it has no UPLOAD phase. Every operation uses the same
53-
# RENDER → COMPILE → PLAN → DEPLOY/PURGE vocabulary.
51+
# that sync local files; purge/test/refresh run entirely on the server against
52+
# already-deployed objects (no local upload), so they have no UPLOAD phase.
53+
# Deploy-family operations share the RENDER → COMPILE → PLAN → DEPLOY/PURGE
54+
# vocabulary; test/refresh are single-phase since they neither render nor plan.
5455
_PHASES_BY_OPERATION: dict[OperationMode, list[str]] = {
5556
"deploy": [UPLOAD_PHASE, *BACKEND_PHASES],
5657
"plan": [UPLOAD_PHASE, *PLAN_OPERATION_PHASES],
5758
"compile": [UPLOAD_PHASE, *COMPILE_OPERATION_PHASES],
5859
"purge": [*BACKEND_PHASES],
60+
"test": ["TEST"],
61+
"refresh": ["REFRESH"],
5962
}
6063

6164
# Only PLAN and DEPLOY emit a meaningful 0-100 progress value.
@@ -273,6 +276,11 @@ def advance_upload(self, count: int = 1) -> None:
273276
self._refresh_display()
274277

275278
def complete_upload(self) -> None:
279+
# Operations that don't sync local files (purge/test/refresh) have no
280+
# UPLOAD phase; nothing to complete. ``run_loader_phase`` calls this
281+
# unconditionally, so the guard keeps single-phase modes working.
282+
if not self._has_upload_phase():
283+
return
276284
if self._upload_file_total > 0:
277285
self._get_phase(UPLOAD_PHASE).observe_running(100, datetime.now())
278286
self._get_phase(UPLOAD_PHASE).observe_done(datetime.now())
@@ -457,14 +465,14 @@ def _append_progress_bar(self, out: Text, progress: int) -> None:
457465
in_progress = 0 < filled < _BAR_WIDTH
458466

459467
if in_progress:
460-
out.append(_BAR_CELL * filled + _BAR_LEADING_EDGE, style="blue")
468+
out.append(_BAR_CELL * filled + _BAR_LEADING_EDGE, style=styles.BLUE)
461469
out.append(_BAR_CELL * (_BAR_WIDTH - filled - 1), style="dim")
462470
elif filled == 0:
463471
out.append(_BAR_CELL * _BAR_WIDTH, style="dim")
464472
else:
465-
out.append(_BAR_CELL * _BAR_WIDTH, style="blue")
473+
out.append(_BAR_CELL * _BAR_WIDTH, style=styles.BLUE)
466474

467-
out.append(f" {progress:>3}%", style="blue")
475+
out.append(f" {progress:>3}%", style=styles.BLUE)
468476

469477
def _append_upload_details(self, out: Text) -> None:
470478
"""Render the stage-creation message and folder counters indented
@@ -509,7 +517,7 @@ def _render_phase_line(self, out: Text, phase: _Phase) -> None:
509517
# Blue matches the active-indicator color used by the
510518
# pip-style progress bar so all "this phase is in flight"
511519
# signals share the same hue.
512-
out.append(_spinner_glyph(), style="blue")
520+
out.append(_spinner_glyph(), style=styles.BLUE)
513521
out.append(duration_str + "\n", style="dim")
514522
else: # PENDING
515523
out.append(name_col + \n", style="dim")

src/snowflake/cli/_plugins/dcm/styles.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,17 @@
1313
# limitations under the License.
1414
from rich.style import Style
1515

16-
_COLOR_BLUE = "#a0a8fe"
16+
# Single source of truth for the DCM "blue" hue. Use the terminal-default
17+
# named color (not a fixed hex) so it respects the user's theme and stays
18+
# identical everywhere it's used: progress spinner/bar, running phase,
19+
# analyze INFO findings / file headers, refresh status, and unknown rows.
20+
BLUE = "blue"
1721

1822
DOMAIN_STYLE = Style(color="cyan")
1923
BOLD_STYLE = Style(bold=True)
2024

2125
# Refresh
22-
STATUS_STYLE = Style(color=_COLOR_BLUE)
26+
STATUS_STYLE = Style(color=BLUE)
2327
REMOVED_STYLE = Style(color="red", italic=True)
2428
INSERTED_STYLE = Style(color="green", italic=True)
2529

@@ -28,18 +32,18 @@
2832
FAIL_STYLE = Style(color="red")
2933
WARNING_STYLE = Style(color="yellow")
3034
# INFO-severity analyze findings: plain blue (distinct from bold-blue file headers).
31-
INFO_STYLE = Style(color="blue")
35+
INFO_STYLE = Style(color=BLUE)
3236

3337
# Plan
3438
CREATE_STYLE = Style(color="green")
3539
ALTER_STYLE = Style(color="yellow")
3640
DROP_STYLE = Style(color="red")
37-
UNKNOWN_STYLE = Style(color=_COLOR_BLUE)
41+
UNKNOWN_STYLE = Style(color=BLUE)
3842

3943
# Deploy progress phases
4044
PHASE_DONE_STYLE = Style(color="green", bold=True)
41-
PHASE_RUNNING_STYLE = Style(color="blue", bold=True)
45+
PHASE_RUNNING_STYLE = Style(color=BLUE, bold=True)
4246
PHASE_FAILED_STYLE = Style(color="red", bold=True)
4347

4448
# Analyze (file/source path headers stand out in bold blue).
45-
FILE_PATH_STYLE = Style(color="blue", bold=True)
49+
FILE_PATH_STYLE = Style(color=BLUE, bold=True)

tests/dcm/__snapshots__/test_commands.ambr

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# name: TestDCMRefresh.test_refresh_with_fresh_tables
33
'''
44

5+
REFRESH ✓ (0.0s)
56

67
UP-TO-DATE 0 0 JW_DCM_TESTALL.ANALYTICS.DYNAMIC_EMPLOYEES
78

@@ -13,6 +14,7 @@
1314
# name: TestDCMRefresh.test_refresh_with_no_dynamic_tables
1415
'''
1516

17+
REFRESH ✓ (0.0s)
1618

1719

1820
No dynamic tables found in the project.
@@ -23,6 +25,7 @@
2325
# name: TestDCMRefresh.test_refresh_with_outdated_tables
2426
'''
2527

28+
REFRESH ✓ (0.0s)
2629

2730
REFRESHED +12.3k -999B JW_DCM_TESTALL.ANALYTICS.DYNAMIC_EMPLOYEES
2831

@@ -34,6 +37,7 @@
3437
# name: TestDCMTest.test_test_all_passing
3538
'''
3639

40+
TEST ✓ (0.0s)
3741

3842
✓ PASS DB.SCHEMA.EMPLOYEES (ROW_COUNT_CHECK)
3943
✓ PASS DB.SCHEMA.ORDERS (NULL_CHECK)
@@ -46,6 +50,7 @@
4650
# name: TestDCMTest.test_test_no_expectations
4751
'''
4852

53+
TEST ✓ (0.0s)
4954

5055

5156
No expectations found in the project.
@@ -56,6 +61,7 @@
5661
# name: TestDCMTest.test_test_with_failures
5762
'''
5863

64+
TEST ✓ (0.0s)
5965

6066
✓ PASS DB.SCHEMA.EMPLOYEES (ROW_COUNT_CHECK)
6167
✗ FAIL DB.SCHEMA.ORDERS (NULL_CHECK)

0 commit comments

Comments
 (0)