Skip to content

feat(deploy): human-readable deploy drift (--diff human)#2984

Open
ronpal wants to merge 6 commits into
mainfrom
feat/deploy-human-readable-diff
Open

feat(deploy): human-readable deploy drift (--diff human)#2984
ronpal wants to merge 6 commits into
mainfrom
feat/deploy-human-readable-diff

Conversation

@ronpal
Copy link
Copy Markdown
Contributor

@ronpal ronpal commented May 6, 2026

Description

What you get: With cdf deploy … --diff human, each resource that differs from CDF is shown in one Rich panel: identifier and file path, a short diff summary, and two columns of YAML (Local build vs CDF, with the project name on the CDF side). Sensitive values are masked the same way as in --verbose; for those resources the long unified verbose diff is not printed, so output stays scannable.

Also in this PR: Deploy summary rows are ordered by impact (creates and updates before unchanged). Human diff YAML keeps build key order and pairs keys sensibly (including a fix when one side is a dict and the other a scalar). Hosted extractor source/destination warnings explain that CDF is compared by external id only. The processed build-directory block uses the same nesting as the plan section so bullets line up. ToolkitPanelSection gains optional content_padding, with padding tuple behavior documented on the panel helpers.

Demo with and without human diff:

Screen.Recording.2026-05-06.at.16.12.16.mov

Bump

  • Patch
  • Skip

Changelog

Added

  • cdf deploy --diff=human and deploy_v2/diff_display.py for structured drift output.

Changed

  • Deployment summary table: sort rows by descending created, updated, deleted, then unchanged.

Improved

  • Hosted extractor source/destination: clearer warning text for id-only CDF comparison.

Fixed

  • Human diff YAML alignment no longer replaces a scalar with {} when the other side is a dict for the same key.

Add --diff human to print a ToolkitPanel summary and a side-by-side
comparison of CDF API dumps vs build YAML for changed resources.
Verbose unified diff is skipped when human diff is selected.
@ronpal ronpal requested review from a team as code owners May 6, 2026 10:27
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 6, 2026

☂️ Python Coverage

current status: ✅

Overall Coverage

Lines Covered Coverage Threshold Status
42658 36623 86% 80% 🟢

New Files

File Coverage Status
cognite_toolkit/_cdf_tk/commands/deploy_v2/diff_display.py 83% 🟢
TOTAL 83% 🟢

Modified Files

File Coverage Status
cognite_toolkit/_cdf_tk/commands/init.py 100% 🟢
cognite_toolkit/_cdf_tk/commands/deploy_v2/command.py 82% 🟢
cognite_toolkit/_cdf_tk/resource_ios/_resource_ios/hosted_extractors.py 91% 🟢
cognite_toolkit/_cdf_tk/ui.py 100% 🟢
TOTAL 93% 🟢

updated for commit: 0f3db0c by action🐍

@codecov
Copy link
Copy Markdown

codecov Bot commented May 6, 2026

Codecov Report

❌ Patch coverage is 85.36585% with 18 lines in your changes missing coverage. Please review.
✅ Project coverage is 85.85%. Comparing base (5d279c4) to head (0f3db0c).

Files with missing lines Patch % Lines
...toolkit/_cdf_tk/commands/deploy_v2/diff_display.py 83.33% 17 Missing ⚠️
...nite_toolkit/_cdf_tk/commands/deploy_v2/command.py 87.50% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff            @@
##             main    #2984    +/-   ##
========================================
  Coverage   85.85%   85.85%            
========================================
  Files         462      463     +1     
  Lines       42543    42658   +115     
========================================
+ Hits        36524    36623    +99     
- Misses       6019     6035    +16     
Files with missing lines Coverage Δ
cognite_toolkit/_cdf_tk/commands/__init__.py 100.00% <100.00%> (ø)
...tk/resource_ios/_resource_ios/hosted_extractors.py 90.76% <100.00%> (+0.07%) ⬆️
cognite_toolkit/_cdf_tk/ui.py 100.00% <100.00%> (ø)
...nite_toolkit/_cdf_tk/commands/deploy_v2/command.py 82.12% <87.50%> (+1.11%) ⬆️
...toolkit/_cdf_tk/commands/deploy_v2/diff_display.py 83.33% <83.33%> (ø)

... and 2 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ronpal
Copy link
Copy Markdown
Contributor Author

ronpal commented May 6, 2026

/gemini review

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a human-readable side-by-side diff view for the deploy command, enabling comparison between local build configurations and the CDF API state. It also implements sorting for deployment results to highlight significant changes and enhances UI components with better documentation and padding control. Feedback was provided regarding a bug in the dictionary alignment logic where type mismatches could cause data loss in the diff display.

Comment on lines +44 to +47
elif isinstance(cv, dict):
out_cdf[k], out_build[k] = _align_nested_dict_pair_for_yaml(cv, {})
elif isinstance(bv, dict):
out_cdf[k], out_build[k] = _align_nested_dict_pair_for_yaml({}, bv)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The current implementation of _align_nested_dict_pair_for_yaml causes data loss when there is a type mismatch between the CDF and local build configurations. If one side is a dictionary and the other is a scalar value (e.g., a string or integer), the scalar value is incorrectly replaced by an empty dictionary {} in the aligned output. This leads to misleading diff results where actual values appear to be replaced by empty objects.

The logic should only align with an empty dictionary if the key is entirely missing from the other side.

Suggested change
elif isinstance(cv, dict):
out_cdf[k], out_build[k] = _align_nested_dict_pair_for_yaml(cv, {})
elif isinstance(bv, dict):
out_cdf[k], out_build[k] = _align_nested_dict_pair_for_yaml({}, bv)
elif isinstance(cv, dict) and k not in build:
out_cdf[k], out_build[k] = _align_nested_dict_pair_for_yaml(cv, {})
elif isinstance(bv, dict) and k not in cdf:
out_cdf[k], out_build[k] = _align_nested_dict_pair_for_yaml({}, bv)

ronpal added 3 commits May 6, 2026 13:31
Gemini review: only recurse with an empty dict when the key is missing
on the other side, not when types disagree (dict vs scalar).

Adds regression tests for dict/scalar mismatches in both directions.
Flatten read-dir summary into the panel section list so bullets line up
with the Plan block. Extract hosted extractor source/destination messages
as constants and explain id-only CDF comparison. Drop unused future
annotations from diff_display per project conventions.
@ronpal
Copy link
Copy Markdown
Contributor Author

ronpal commented May 6, 2026

/gemini review

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a human-readable diff view for the deploy v2 command, allowing users to see side-by-side comparisons between local build YAMLs and CDF API data. It also includes UI enhancements to ToolkitPanel and ToolkitPanelSection to support better content padding and layout. Feedback was provided regarding missing or non-standard docstrings for the new diff display functions to ensure compliance with the repository style guide.

Comment on lines +24 to +34
def _align_nested_dict_pair_for_yaml(cdf: Any, build: Any) -> tuple[Any, Any]:
"""Return copies of ``cdf`` / ``build`` with identical dict key order for paired dumps.

Order at each dict level: keys from **build** (insertion order), then keys only in **CDF**
(insertion order). Nested dict values are aligned the same way so ``yaml_safe_dump(...,
sort_keys=False)`` produces line-aligned text without global alphabetical sorting.

Non-dict values and mismatched types are left unchanged; lists are not reordered.
Empty ``{}`` is used only when a dict exists on one side and the **key** is absent on the other,
so scalar-vs-dict mismatches are not coerced to ``{}``.
"""
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The docstring for _align_nested_dict_pair_for_yaml does not follow the required Args: and Returns: format specified in the repository style guide. Please update it to adhere to the standard format.

def _align_nested_dict_pair_for_yaml(cdf: Any, build: Any) -> tuple[Any, Any]:
    """
    Return copies of ``cdf`` / ``build`` with identical dict key order for paired dumps.

    Order at each dict level: keys from **build** (insertion order), then keys only in **CDF**
    (insertion order). Nested dict values are aligned the same way so ``yaml_safe_dump(...,
    sort_keys=False)`` produces line-aligned text without global alphabetical sorting.

    Non-dict values and mismatched types are left unchanged; lists are not reordered.
    Empty ``{}`` is used only when a dict exists on one side and the **key** is absent on the other,
    so scalar-vs-dict mismatches are not coerced to ``{}``.

    Args:
        cdf: The CDF resource data.
        build: The local build resource data.

    Returns:
        tuple[Any, Any]: A tuple containing the aligned (cdf, build) data.
    """
References
  1. Use concise docstrings with Args/Returns format. (link)

Comment on lines +143 to +152
def render_deploy_human_diff(
*,
resource_name: str,
identifier: Any,
source_file: Path,
cdf_dict: dict[str, Any],
yaml_dict: dict[str, Any],
sensitive_strings: Iterable[str],
cdf_project: str,
) -> ToolkitPanel:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The render_deploy_human_diff function is missing a docstring. According to the repository style guide, all functions must have concise docstrings using the Args: and Returns: format.

def render_deploy_human_diff(
    *,
    resource_name: str,
    identifier: Any,
    source_file: Path,
    cdf_dict: dict[str, Any],
    yaml_dict: dict[str, Any],
    sensitive_strings: Iterable[str],
    cdf_project: str,
) -> ToolkitPanel:
    """
    Renders a human-readable diff panel for a resource.

    Args:
        resource_name: The display name of the resource type.
        identifier: The unique identifier of the resource.
        source_file: The path to the local source file.
        cdf_dict: The resource data as retrieved from CDF.
        yaml_dict: The resource data as defined in the local build.
        sensitive_strings: Strings to be masked in the output.
        cdf_project: The name of the CDF project.

    Returns:
        ToolkitPanel: A panel containing the diff view.
    """
References
  1. All functions, methods, and class attributes must have type hints. Use concise docstrings with Args/Returns format. (link)

"--diff",
help="When set to 'human', show a ToolkitPanel summary and a side-by-side line comparison of "
"CDF (API) dump vs build YAML for each resource that would change.",
),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

New feature, alpha flag?

left = cdf_lines[i1:i2]
right = yaml_lines[j1:j2]
for k in range(max(len(left), len(right))):
l_txt = left[k] if k < len(left) else ""
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
l_txt = left[k] if k < len(left) else ""
left_text = left[k] if k < len(left) else ""

right = yaml_lines[j1:j2]
for k in range(max(len(left), len(right))):
l_txt = left[k] if k < len(left) else ""
r_txt = right[k] if k < len(right) else ""
Copy link
Copy Markdown
Contributor

@Magssch Magssch May 7, 2026

Choose a reason for hiding this comment

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

Suggested change
r_txt = right[k] if k < len(right) else ""
right_text = right[k] if k < len(right) else ""

for k in range(max(len(left), len(right))):
l_txt = left[k] if k < len(left) else ""
r_txt = right[k] if k < len(right) else ""
yield (Text(l_txt, style="yellow"), Text(r_txt, style="cyan"))
Copy link
Copy Markdown
Contributor

@Magssch Magssch May 7, 2026

Choose a reason for hiding this comment

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

Suggested change
yield (Text(l_txt, style="yellow"), Text(r_txt, style="cyan"))
yield (Text(left_text, style="yellow"), Text(right_text, style="cyan"))

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