From 3404967f76a01413336439989f9ca34e2758c840 Mon Sep 17 00:00:00 2001 From: busya Date: Fri, 10 Jul 2026 10:55:10 +0300 Subject: [PATCH] feat(git): add environment timeline visualization and branch commit APIs Implement a new Git environment timeline component to visualize dashboard versions across different deployment stages (Development, Pre-production, and Production). This includes new backend endpoints and frontend services to support historical data retrieval and version comparison. - Add `get_branch_commits` and `get_commit_diff` endpoints to backend API and Git service. - Implement `GitEnvironmentTimeline` Svelte component for visual representation of deployment history. - Update `GitManagerModel` to manage timeline state, including environment histories and version selection for comparison. - Add `getBranchCommits` and `getCommitDiff` methods to `gitService`. - Improve UX by separating the branch selector from the version map. - Add comprehensive i18n support for the new visualization features. --- .gitignore | 1 + backend/src/api/routes/git/__init__.py | 2 +- .../api/routes/git/_repo_operations_routes.py | 48 ++ backend/src/services/git/_status.py | 46 ++ .../lib/components/git/BranchSelector.svelte | 6 +- .../git/GitEnvironmentTimeline.svelte | 589 ++++++++++++++++++ .../src/lib/components/git/GitManager.svelte | 47 +- .../components/git/GitWorkspacePanel.svelte | 2 +- frontend/src/lib/i18n/locales/en/git.json | 65 +- frontend/src/lib/i18n/locales/ru/git.json | 65 +- .../src/lib/models/GitManagerModel.svelte.ts | 69 ++ frontend/src/services/gitService.ts | 19 + 12 files changed, 947 insertions(+), 12 deletions(-) create mode 100644 frontend/src/lib/components/git/GitEnvironmentTimeline.svelte diff --git a/.gitignore b/.gitignore index 434fd2dfc..6852f6a75 100755 --- a/.gitignore +++ b/.gitignore @@ -110,3 +110,4 @@ superset-tools.bundle # Generated audit reports axiom-mcp-tools-audit-report.md *.docx +backend/relative \ No newline at end of file diff --git a/backend/src/api/routes/git/__init__.py b/backend/src/api/routes/git/__init__.py index 226f1d5c6..06f71b090 100644 --- a/backend/src/api/routes/git/__init__.py +++ b/backend/src/api/routes/git/__init__.py @@ -37,7 +37,7 @@ from ._merge_routes import abort_merge, continue_merge, get_merge_conflicts, get from ._repo_lifecycle_routes import deploy_dashboard, promote_dashboard, sync_dashboard # noqa: F401 # -- Repo operations routes (commit, push, pull, status, diff, history, generate-message) -- -from ._repo_operations_routes import commit_changes, generate_commit_message, get_history, get_repository_diff, get_repository_status, get_repository_status_batch, pull_changes, push_changes, rollback_commit # noqa: F401 +from ._repo_operations_routes import commit_changes, generate_commit_message, get_branch_commits, get_commit_diff, get_history, get_repository_diff, get_repository_status, get_repository_status_batch, pull_changes, push_changes, rollback_commit # noqa: F401 # -- Repo routes (core) -- from ._repo_routes import checkout_branch, create_branch, delete_branch, delete_repository, get_branch_protection_rules, get_branches, get_repository_binding, init_repository # noqa: F401 diff --git a/backend/src/api/routes/git/_repo_operations_routes.py b/backend/src/api/routes/git/_repo_operations_routes.py index bed3d3e47..d9c4b5359 100644 --- a/backend/src/api/routes/git/_repo_operations_routes.py +++ b/backend/src/api/routes/git/_repo_operations_routes.py @@ -305,6 +305,54 @@ async def get_history( # #endregion get_history +# #region get_branch_commits [C:2] +# @BRIEF Per-branch commit list for visualization lanes (recent window recommended, 20 for BI history context). +@router.get("/repositories/{dashboard_ref}/branches/{branch_name}/commits", response_model=list[CommitSchema]) +async def get_branch_commits( + dashboard_ref: str, + branch_name: str, + limit: int = 20, + env_id: str | None = None, + config_manager=Depends(get_config_manager), + _=Depends(has_permission("plugin:git", "EXECUTE")), +): + _gs = get_git_service() + with belief_scope("get_branch_commits"): + from . import _resolve_dashboard_id_from_ref + try: + dashboard_id = await _resolve_dashboard_id_from_ref(dashboard_ref, config_manager, env_id) + return await _await_service_result(_gs.get_branch_commits(dashboard_id, branch_name, limit)) + except HTTPException: + raise + except Exception as e: + _handle_unexpected_git_route_error("get_branch_commits", e) +# #endregion get_branch_commits + + +# #region get_commit_diff [C:2] +@router.get("/repositories/{dashboard_ref}/commits/diff") +async def get_commit_diff( + dashboard_ref: str, + from_ref: str, + to_ref: str | None = None, + env_id: str | None = None, + config_manager=Depends(get_config_manager), + _=Depends(has_permission("plugin:git", "EXECUTE")), +): + _gs = get_git_service() + with belief_scope("get_commit_diff"): + from . import _resolve_dashboard_id_from_ref + try: + dashboard_id = await _resolve_dashboard_id_from_ref(dashboard_ref, config_manager, env_id) + diff = await _await_service_result(_gs.get_commit_diff(dashboard_id, from_ref, to_ref)) + return {"from": from_ref, "to": to_ref, "diff": diff} + except HTTPException: + raise + except Exception as e: + _handle_unexpected_git_route_error("get_commit_diff", e) +# #endregion get_commit_diff + + # #region generate_commit_message [C:3] [TYPE Function] # @ingroup Api # @BRIEF Generate a suggested commit message using LLM. diff --git a/backend/src/services/git/_status.py b/backend/src/services/git/_status.py index 8a1b2a08f..d2c89041c 100644 --- a/backend/src/services/git/_status.py +++ b/backend/src/services/git/_status.py @@ -182,6 +182,52 @@ class GitServiceStatusMixin: return commits # endregion get_commit_history + # region get_branch_commits [C:4] [TYPE Function] [SEMANTICS git,history,per-branch,lock] + # @PURPOSE: Retrieve commit history for a *specific branch* (for lane visualization). + # Uses iter_commits on the branch ref. Concurrent-safe. + # @PARAM branch (str) - Branch name (e.g. "dev", "preprod", "prod", or feature/*) + # @PARAM limit (int) - Max commits per branch for the viz (recent window). 20 chosen for BI analyst promotion audit depth. + async def get_branch_commits(self, dashboard_id: int, branch: str, limit: int = 15) -> list[dict]: + with self._locked(dashboard_id): + with belief_scope("GitService.get_branch_commits"): + repo = await self.get_repo(dashboard_id) + commits = [] + try: + if not repo.heads and not repo.remotes: + return [] + # iter_commits accepts branch name or ref + for commit in repo.iter_commits(branch, max_count=limit): + commits.append({ + "hash": commit.hexsha, + "author": commit.author.name, + "email": commit.author.email, + "timestamp": datetime.fromtimestamp(commit.committed_date), + "message": commit.message.strip(), + "files_changed": list(commit.stats.files.keys()), + "branch": branch, + }) + except Exception as e: + logger.explore(f"Could not retrieve commits for branch {branch} on dashboard {dashboard_id}: {e}", extra={"src": "get_branch_commits"}) + return [] + return commits + # endregion get_branch_commits + + # region get_commit_diff [C:3] [TYPE Function] [SEMANTICS git,diff,historical] + # @PURPOSE: Return unified or raw diff between two commit-ish (for viz "diff between versions"). + # Falls back to working tree behavior if to_ref is None. + async def get_commit_diff(self, dashboard_id: int, from_ref: str, to_ref: str | None = None) -> str: + with self._locked(dashboard_id): + with belief_scope("GitService.get_commit_diff"): + repo = await self.get_repo(dashboard_id) + try: + if to_ref: + return repo.git.diff(from_ref, to_ref) + return repo.git.diff(from_ref) + except Exception as e: + logger.explore(f"get_commit_diff failed for {from_ref}..{to_ref} on {dashboard_id}: {e}", extra={"src": "get_commit_diff"}) + raise + # endregion get_commit_diff + # region rollback_commit [C:3] [TYPE Function] [SEMANTICS git,history,rollback,lock] # @PURPOSE: Roll back one commit by creating a revert commit (concurrent-safe). # @PRE Repository for dashboard_id exists and commit_hash identifies an existing commit. diff --git a/frontend/src/lib/components/git/BranchSelector.svelte b/frontend/src/lib/components/git/BranchSelector.svelte index e8d374b32..c94ce7686 100644 --- a/frontend/src/lib/components/git/BranchSelector.svelte +++ b/frontend/src/lib/components/git/BranchSelector.svelte @@ -126,7 +126,7 @@
-
+