From 14a77899a5a35553666a7d15d08e9b68d85653ea Mon Sep 17 00:00:00 2001 From: altinity-robot Date: Fri, 24 Jul 2026 09:35:01 -0400 Subject: [PATCH 1/5] Fix release report baseline detection Deepen shallow history incrementally so release reports can find the oldest bootstrap without silently excluding earlier unreleased merges. Co-authored-by: Cursor --- .../create_workflow_report.py | 39 ++++++++++++------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/.github/actions/create_workflow_report/create_workflow_report.py b/.github/actions/create_workflow_report/create_workflow_report.py index 32d6a1fc5938..f5921c026af0 100755 --- a/.github/actions/create_workflow_report/create_workflow_report.py +++ b/.github/actions/create_workflow_report/create_workflow_report.py @@ -311,9 +311,7 @@ def _find_rebase_baseline(branch_ref: str, cwd: str | None) -> str | None: "git", "log", branch_ref, - "--first-parent", - "-n", - "1", + "--reverse", "-i", "-E", "--grep=^Rebase CICD", @@ -342,7 +340,7 @@ def get_prs_in_release_dataframe( ) -> pd.DataFrame: f""" PRs merged into branch_ref that belong in the next release notes: after the latest GitHub - Release tag on this history, or after the latest bootstrap merge if no such tag exists. + Release tag on this history, or after the oldest rebase bootstrap if no such tag exists. Only merge commits whose subject has from / (e.g. from Altinity/) are included. Columns: pr_number, pr_name, labels. Omits PRs labeled cicd. """ @@ -350,7 +348,14 @@ def get_prs_in_release_dataframe( if not branch_sha: raise Exception(f"Cannot resolve branch ref: {branch_ref!r}") - # CI often checks out with --depth=1 and --no-tags; fetch enough history and tags once. + # CI often checks out with --depth=1 and --no-tags; fetch tags once, then deepen + # in steps until a baseline is reachable. + # Observed baseline distances are ~1k commits and grow slowly, so DEEPEN_CAP sits + # well above that; the cap exists so a failed lookup fails fast instead of fetching + # the branch's entire (100k+ commit) history. + DEEPEN_STEP = 250 + DEEPEN_CAP = 10000 + subprocess.run( ["git", "fetch", "--tags", "origin"], cwd=cwd, @@ -359,22 +364,26 @@ def get_prs_in_release_dataframe( check=False, ) - _, baseline_sha = _find_release_baseline(branch_ref, repo, cwd) - if not baseline_sha: - # If no release tag, search recent history for the latest branch bootstrap merge. + baseline_sha = None + for _ in range(DEEPEN_CAP // DEEPEN_STEP): subprocess.run( - ["git", "fetch", "--deepen=500", "origin", branch_ref], + ["git", "fetch", f"--deepen={DEEPEN_STEP}", "origin", branch_ref], cwd=cwd, capture_output=True, text=True, check=False, ) - rebase_sha = _find_rebase_baseline(branch_ref, cwd) - if not rebase_sha: - raise Exception( - "No release tag on this branch and no rebase bootstrap merge found" - ) - baseline_sha = rebase_sha + _, baseline_sha = _find_release_baseline(branch_ref, repo, cwd) + if not baseline_sha: + baseline_sha = _find_rebase_baseline(branch_ref, cwd) + if baseline_sha: + break + + if not baseline_sha: + raise Exception( + "No release tag on this branch and no rebase bootstrap merge found " + f"within {DEEPEN_CAP} commits" + ) df = _git_log_merge_prs(baseline_sha, branch_ref, cwd, repo) return _enrich_prs_in_release_merge_prs(df, repo) From 34326dd0ed8d43c363174103f49dafd94127df06 Mon Sep 17 00:00:00 2001 From: altinity-robot Date: Fri, 24 Jul 2026 10:07:29 -0400 Subject: [PATCH 2/5] report now discovers known fails path automatically --- .../actions/create_workflow_report/action.yml | 2 +- .../create_workflow_report.py | 16 ++++++++++------ .../workflow_report_hook.sh | 2 +- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/.github/actions/create_workflow_report/action.yml b/.github/actions/create_workflow_report/action.yml index 5e0f517c35a0..fa7c0fe26219 100644 --- a/.github/actions/create_workflow_report/action.yml +++ b/.github/actions/create_workflow_report/action.yml @@ -30,7 +30,7 @@ runs: pip install clickhouse-driver==0.2.8 numpy==1.26.4 pandas==2.0.3 jinja2==3.1.5 CMD="python3 .github/actions/create_workflow_report/create_workflow_report.py" - ARGS="--actions-run-url $ACTIONS_RUN_URL --known-fails tests/broken_tests.yaml --cves --pr-number $PR_NUMBER" + ARGS="--actions-run-url $ACTIONS_RUN_URL --known-fails --cves --pr-number $PR_NUMBER" set +e -x if [[ "$FINAL" == "false" ]]; then diff --git a/.github/actions/create_workflow_report/create_workflow_report.py b/.github/actions/create_workflow_report/create_workflow_report.py index f5921c026af0..1e9e4bce21d0 100755 --- a/.github/actions/create_workflow_report/create_workflow_report.py +++ b/.github/actions/create_workflow_report/create_workflow_report.py @@ -31,6 +31,10 @@ S3_BUCKET = "altinity-build-artifacts" GITHUB_REPO = "Altinity/ClickHouse" GITHUB_TOKEN = os.getenv("GITHUB_TOKEN") or os.getenv("GH_TOKEN") +# Resolve from this file so discovery works from repo root or the action folder. +KNOWN_FAILS_FILE = ( + Path(__file__).resolve().parents[3] / "tests" / "broken_tests.yaml" +) CVE_SEVERITY_ORDER = {"critical": 1, "high": 2, "medium": 3, "low": 4, "negligible": 5} @@ -1015,7 +1019,7 @@ def parse_args() -> argparse.Namespace: "--no-upload", action="store_true", help="Do not upload the report" ) parser.add_argument( - "--known-fails", type=str, help="Path to the file with known fails" + "--known-fails", action="store_true", help="Check known fails" ) parser.add_argument( "--cves", action="store_true", help="Get CVEs from Grype results" @@ -1031,7 +1035,7 @@ def create_workflow_report( pr_number: int = None, commit_sha: str = None, no_upload: bool = False, - known_fails_file_path: str = None, + check_known_fails: bool = False, check_cves: bool = False, mark_preview: bool = False, ) -> str: @@ -1103,11 +1107,11 @@ def create_workflow_report( # This might occur when run in preview mode. cves_not_checked = not check_cves or results_dfs["docker_images_cves"] is ... - if known_fails_file_path: - if not os.path.exists(known_fails_file_path): - print(f"WARNING:Known fails file {known_fails_file_path} not found.") + if check_known_fails: + if not KNOWN_FAILS_FILE.exists(): + print(f"WARNING:Known fails file {KNOWN_FAILS_FILE} not found.") else: - known_fails = get_broken_tests_rules(known_fails_file_path) + known_fails = get_broken_tests_rules(str(KNOWN_FAILS_FILE)) results_dfs["checks_known_fails"] = get_checks_known_fails( db_client, commit_sha, branch_name, known_fails diff --git a/.github/actions/create_workflow_report/workflow_report_hook.sh b/.github/actions/create_workflow_report/workflow_report_hook.sh index 04a09a9ee3ca..06edf8867fb8 100755 --- a/.github/actions/create_workflow_report/workflow_report_hook.sh +++ b/.github/actions/create_workflow_report/workflow_report_hook.sh @@ -1,7 +1,7 @@ #!/bin/bash # This script is for generating preview reports when invoked as a post-hook from a praktika job pip install clickhouse-driver==0.2.8 numpy==1.26.4 pandas==2.0.3 jinja2==3.1.5 -ARGS="--mark-preview --known-fails tests/broken_tests.yaml --cves --actions-run-url $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID --pr-number $PR_NUMBER" +ARGS="--mark-preview --known-fails --cves --actions-run-url $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID --pr-number $PR_NUMBER" CMD="python3 .github/actions/create_workflow_report/create_workflow_report.py" $CMD $ARGS From 8091835af42c843c08de510c24a4dfc673fda29e Mon Sep 17 00:00:00 2001 From: altinity-robot Date: Fri, 24 Jul 2026 12:07:20 -0400 Subject: [PATCH 3/5] Improve release report PR cross-check and label checks Add a GitHub search link based on the release baseline date, map tag-triggered refs to the PR base branch, and require verified plus expected version labels. Co-authored-by: Cursor --- .../ci_run_report.html.jinja | 3 + .../create_workflow_report.py | 116 ++++++++++++++++-- 2 files changed, 106 insertions(+), 13 deletions(-) diff --git a/.github/actions/create_workflow_report/ci_run_report.html.jinja b/.github/actions/create_workflow_report/ci_run_report.html.jinja index 92af0fab1cf0..48ae95cefba1 100644 --- a/.github/actions/create_workflow_report/ci_run_report.html.jinja +++ b/.github/actions/create_workflow_report/ci_run_report.html.jinja @@ -243,6 +243,9 @@ {%- if pr_number == 0 %}
+ {%- if prs_in_release_search_url %} +

See the PRs on GitHub

+ {%- endif %} {{ prs_in_release_html }}
{%- endif %} diff --git a/.github/actions/create_workflow_report/create_workflow_report.py b/.github/actions/create_workflow_report/create_workflow_report.py index 1e9e4bce21d0..a12f462a7a35 100755 --- a/.github/actions/create_workflow_report/create_workflow_report.py +++ b/.github/actions/create_workflow_report/create_workflow_report.py @@ -279,9 +279,11 @@ def _git_log_merge_prs( def _find_release_baseline( branch_ref: str, repo: str, cwd: str | None -) -> tuple[str | None, str | None]: +) -> tuple[str | None, str | None, str | None]: + """Return (tag_name, tag_sha, published_date YYYY-MM-DD) for the latest + non-draft release tag that is an ancestor of branch_ref.""" if not GITHUB_TOKEN: - return None, None + return None, None, None headers = { "Authorization": f"token {GITHUB_TOKEN}", "Accept": "application/vnd.github.v3+json", @@ -305,8 +307,10 @@ def _find_release_baseline( continue if not _git_is_ancestor(tag_sha, branch_ref, cwd): continue - return tag_name, tag_sha - return None, None + published_at = rel.get("published_at") or rel.get("created_at") or "" + published_date = published_at[:10] if published_at else None + return tag_name, tag_sha, published_date + return None, None, None def _find_rebase_baseline(branch_ref: str, cwd: str | None) -> str | None: @@ -336,17 +340,33 @@ def _find_rebase_baseline(branch_ref: str, cwd: str | None) -> str | None: return lines[0] +def _git_commit_date(sha: str, cwd: str | None) -> str | None: + p = subprocess.run( + ["git", "show", "-s", "--format=%cs", sha], + cwd=cwd, + capture_output=True, + text=True, + check=False, + ) + if p.returncode != 0: + return None + date = p.stdout.strip() + return date or None + + def get_prs_in_release_dataframe( branch_ref: str = "HEAD", *, repo: str = GITHUB_REPO, cwd: str, -) -> pd.DataFrame: +) -> tuple[pd.DataFrame, str | None]: f""" PRs merged into branch_ref that belong in the next release notes: after the latest GitHub Release tag on this history, or after the oldest rebase bootstrap if no such tag exists. Only merge commits whose subject has from / (e.g. from Altinity/) are included. Columns: pr_number, pr_name, labels. Omits PRs labeled cicd. + + Returns (dataframe, baseline_date YYYY-MM-DD or None). """ branch_sha = _git_rev_parse(branch_ref, cwd) if not branch_sha: @@ -369,6 +389,7 @@ def get_prs_in_release_dataframe( ) baseline_sha = None + baseline_date = None for _ in range(DEEPEN_CAP // DEEPEN_STEP): subprocess.run( ["git", "fetch", f"--deepen={DEEPEN_STEP}", "origin", branch_ref], @@ -377,9 +398,13 @@ def get_prs_in_release_dataframe( text=True, check=False, ) - _, baseline_sha = _find_release_baseline(branch_ref, repo, cwd) + _, baseline_sha, baseline_date = _find_release_baseline( + branch_ref, repo, cwd + ) if not baseline_sha: baseline_sha = _find_rebase_baseline(branch_ref, cwd) + if baseline_sha: + baseline_date = _git_commit_date(baseline_sha, cwd) if baseline_sha: break @@ -390,7 +415,24 @@ def get_prs_in_release_dataframe( ) df = _git_log_merge_prs(baseline_sha, branch_ref, cwd, repo) - return _enrich_prs_in_release_merge_prs(df, repo) + return _enrich_prs_in_release_merge_prs(df, repo), baseline_date + + +def _prs_in_release_search_url(branch_name: str, baseline_date: str | None) -> str | None: + if not baseline_date: + return None + # Tag-triggered runs expose the tag as head_branch; search needs the PR base + # branch (e.g. v25.8.28.10001.altinitystable → stable-25.8). + match = re.match( + r"^v(\d+)\.(\d+)\.\d+\.\d+\.altinity(stable|antalya)$", branch_name + ) + if match: + branch_name = f"{match.group(3)}-{match.group(1)}.{match.group(2)}" + query = f"is:pr base:{branch_name} merged:>{baseline_date}" + return ( + f"https://github.com/{GITHUB_REPO}/pulls?" + + urllib.parse.urlencode({"q": query}) + ) def _checks_latest_test_status_cte(commit_sha: str, branch_name: str) -> str: @@ -723,6 +765,37 @@ def get_cached_job(job_name: str) -> dict: return workflow_config["cache_jobs"].get(job_name, {}) +@lru_cache +def get_expected_version_labels() -> list[str]: + """Return required version labels from workflow_config.custom_data.version. + + Stable releases use the minor and full version labels. Antalya releases use + ``antalya``, ``antalya-{major}.{minor}``, and ``antalya-{full_version}``. + Returns an empty list when version metadata is unavailable. + """ + try: + version = get_workflow_config().get("custom_data", {}).get("version") or {} + major = version.get("major") + minor = version.get("minor") + patch = version.get("patch") + tweak = version.get("tweak") + flavour = version.get("flavour") + if None in (major, minor, patch, tweak, flavour): + print("WARNING:Incomplete version metadata in workflow config.") + return [] + minor_version = f"{major}.{minor}" + full_version = f"{minor_version}.{patch}.{tweak}" + if flavour == "altinityantalya": + return ["antalya", f"antalya-{minor_version}", f"antalya-{full_version}"] + if flavour in ["altinitystable", "altinitytest"]: + return [minor_version, full_version] + print(f"WARNING:Unknown release flavour in workflow config: {flavour}") + return [] + except Exception as e: + print(f"WARNING:Could not read expected version labels: {e}") + return [] + + def get_cves(pr_number, commit_sha, branch): """ Fetch Grype results from S3. @@ -836,12 +909,20 @@ def format_pr_labels_with_verification(labels: str) -> str: Expects ``labels`` to be already HTML-escaped at collection time (see _enrich_prs_in_release_merge_prs). + + Requires ``verified`` plus expected minor and full version labels when + version metadata is available. """ - labels_list = labels.split(", ") - if "verified" in labels_list: + labels_list = labels.split(", ") if labels else [] + required = ["verified"] + get_expected_version_labels() + missing = [label for label in required if label not in labels_list] + if not missing: return labels - else: - return f'{labels} (missing verification)' + missing_text = html.escape(", ".join(missing), quote=True) + return ( + f'' + f"{labels} (missing: {missing_text})" + ) def format_test_name_for_linewrap(text: str) -> str: @@ -1088,11 +1169,17 @@ def create_workflow_report( "docker_images_cves": [], } known_fails = {} + prs_in_release_search_url = None if pr_number == 0 and not mark_preview: try: - prs_df = get_prs_in_release_dataframe(branch_name, cwd=os.getcwd()) + prs_df, baseline_date = get_prs_in_release_dataframe( + branch_name, cwd=os.getcwd() + ) results_dfs["prs_in_release"] = prs_df + prs_in_release_search_url = _prs_in_release_search_url( + branch_name, baseline_date + ) except Exception as e: print(f"Error in get_prs_in_release_dataframe: {e}") @@ -1184,10 +1271,13 @@ def create_workflow_report( "build_report_links": get_build_report_links( results_dfs["job_statuses"], pr_number, branch_name, commit_sha ), + "prs_in_release_search_url": prs_in_release_search_url, "prs_in_release_html": ( "

PR details are not loaded during preview.

" if mark_preview or pr_number != 0 - else format_results_as_html_table(results_dfs["prs_in_release"]) + else format_results_as_html_table( + results_dfs["prs_in_release"] + ) ), "ci_jobs_status_html": format_results_as_html_table( results_dfs["job_statuses"] From 576f8c0079a6de5a45585eae20d68d30c989aa75 Mon Sep 17 00:00:00 2001 From: altinity-robot Date: Mon, 27 Jul 2026 08:53:42 -0400 Subject: [PATCH 4/5] report now fetches workflow config for cached jobs from s3, instead of disk --- .../actions/create_workflow_report/action.yml | 11 -- .../create_workflow_report.py | 119 +++++++++++------- .github/workflows/master.yml | 1 - .github/workflows/pull_request.yml | 1 - .github/workflows/release_builds.yml | 1 - ci/praktika/yaml_additional_templates.py | 1 - 6 files changed, 75 insertions(+), 59 deletions(-) diff --git a/.github/actions/create_workflow_report/action.yml b/.github/actions/create_workflow_report/action.yml index fa7c0fe26219..7ede97fbaed1 100644 --- a/.github/actions/create_workflow_report/action.yml +++ b/.github/actions/create_workflow_report/action.yml @@ -1,9 +1,6 @@ name: Create and Upload Combined Report description: Create and upload a combined CI report inputs: - workflow_config: - description: "Workflow config" - required: true final: description: "Control whether the report is final or a preview" required: false @@ -11,14 +8,6 @@ inputs: runs: using: "composite" steps: - - name: Create workflow config - shell: bash - run: | - mkdir -p ./ci/tmp - cat > ./ci/tmp/workflow_status.json << 'EOF' - ${{ inputs.workflow_config }} - EOF - - name: Create and upload workflow report env: PR_NUMBER: ${{ github.event.pull_request.number || 0 }} diff --git a/.github/actions/create_workflow_report/create_workflow_report.py b/.github/actions/create_workflow_report/create_workflow_report.py index a12f462a7a35..8bb928c5b51a 100755 --- a/.github/actions/create_workflow_report/create_workflow_report.py +++ b/.github/actions/create_workflow_report/create_workflow_report.py @@ -10,7 +10,6 @@ from datetime import datetime from datetime import timezone from functools import lru_cache -from glob import glob import urllib.parse import re import subprocess @@ -736,45 +735,64 @@ def get_new_fails_this_pr( return new_fails_df +def _workflow_config_s3_url(pr_number: int, commit_sha: str, ref_name: str) -> str: + if pr_number == 0: + # REF uploads use a double slash before config_workflow in the S3 key. + return ( + f"https://{S3_BUCKET}.s3.amazonaws.com/REFs/{ref_name}/{commit_sha}/" + "/config_workflow/workflow_config_masterci.json" + ) + return ( + f"https://{S3_BUCKET}.s3.amazonaws.com/PRs/{pr_number}/{commit_sha}/" + "config_workflow/workflow_config_pr.json" + ) + + @lru_cache -def get_workflow_config() -> dict: - - # 25.12+ - if os.path.exists("./ci/tmp/workflow_status.json"): - with open("./ci/tmp/workflow_status.json", "r") as f: - data = json.load(f)["config_workflow"]["outputs"]["data"] - assert data is not None, "data is None" - if isinstance(data, str): - data = json.loads(data) - assert ( - "WORKFLOW_CONFIG" in data.keys() - ), f"WORKFLOW_CONFIG not found in data: {data.keys()}" - return data["WORKFLOW_CONFIG"] - - workflow_config_files = glob("./ci/tmp/workflow_config*.json") - if len(workflow_config_files) == 0: - raise Exception("No workflow config file found") - if len(workflow_config_files) > 1: - raise Exception("Multiple workflow config files found") - with open(workflow_config_files[0], "r") as f: - return json.load(f) - - -def get_cached_job(job_name: str) -> dict: - workflow_config = get_workflow_config() - return workflow_config["cache_jobs"].get(job_name, {}) +def get_workflow_config(pr_number: int, commit_sha: str, ref_name: str) -> dict: + """Fetch workflow config published by Config Workflow from S3. + + On failure, warn and return a minimal empty config (same shape recreation uses). + """ + url = _workflow_config_s3_url(pr_number, commit_sha, ref_name) + try: + response = requests.get(url, timeout=30) + if response.status_code != 200: + print( + f"WARNING:Failed to fetch workflow config from {url}: " + f"{response.status_code}" + ) + return {"cache_jobs": {}} + return response.json() + except Exception as e: + print(f"WARNING:Failed to fetch workflow config from {url}: {e}") + return {"cache_jobs": {}} + + +def get_cached_job( + job_name: str, pr_number: int, commit_sha: str, ref_name: str +) -> dict: + workflow_config = get_workflow_config(pr_number, commit_sha, ref_name) + return workflow_config.get("cache_jobs", {}).get(job_name, {}) @lru_cache -def get_expected_version_labels() -> list[str]: +def get_expected_version_labels( + pr_number: int, commit_sha: str, ref_name: str +) -> tuple[str, ...]: """Return required version labels from workflow_config.custom_data.version. Stable releases use the minor and full version labels. Antalya releases use ``antalya``, ``antalya-{major}.{minor}``, and ``antalya-{full_version}``. - Returns an empty list when version metadata is unavailable. + Returns an empty tuple when version metadata is unavailable. """ try: - version = get_workflow_config().get("custom_data", {}).get("version") or {} + version = ( + get_workflow_config(pr_number, commit_sha, ref_name) + .get("custom_data", {}) + .get("version") + or {} + ) major = version.get("major") minor = version.get("minor") patch = version.get("patch") @@ -782,18 +800,18 @@ def get_expected_version_labels() -> list[str]: flavour = version.get("flavour") if None in (major, minor, patch, tweak, flavour): print("WARNING:Incomplete version metadata in workflow config.") - return [] + return () minor_version = f"{major}.{minor}" full_version = f"{minor_version}.{patch}.{tweak}" if flavour == "altinityantalya": - return ["antalya", f"antalya-{minor_version}", f"antalya-{full_version}"] + return ("antalya", f"antalya-{minor_version}", f"antalya-{full_version}") if flavour in ["altinitystable", "altinitytest"]: - return [minor_version, full_version] + return (minor_version, full_version) print(f"WARNING:Unknown release flavour in workflow config: {flavour}") - return [] + return () except Exception as e: print(f"WARNING:Could not read expected version labels: {e}") - return [] + return () def get_cves(pr_number, commit_sha, branch): @@ -811,7 +829,9 @@ def format_prefix(pr_number, commit_sha, branch): else: return f"PRs/{pr_number}/{commit_sha}/grype/" - cached_server_job = get_cached_job("Docker server image") + cached_server_job = get_cached_job( + "Docker server image", pr_number, commit_sha, branch + ) if cached_server_job: prefixes_to_check.add( format_prefix( @@ -820,7 +840,9 @@ def format_prefix(pr_number, commit_sha, branch): cached_server_job["branch"], ) ) - cached_keeper_job = get_cached_job("Docker keeper image") + cached_keeper_job = get_cached_job( + "Docker keeper image", pr_number, commit_sha, branch + ) if cached_keeper_job: prefixes_to_check.add( format_prefix( @@ -904,17 +926,19 @@ def url_to_html_link(url: str) -> str: return f'{text}' -def format_pr_labels_with_verification(labels: str) -> str: +def format_pr_labels_with_verification( + labels: str, required_labels: tuple[str, ...] = () +) -> str: """Format the PR labels with verification. Expects ``labels`` to be already HTML-escaped at collection time (see _enrich_prs_in_release_merge_prs). - Requires ``verified`` plus expected minor and full version labels when - version metadata is available. + Requires ``verified`` plus ``required_labels`` (the expected version labels + when version metadata is available). """ labels_list = labels.split(", ") if labels else [] - required = ["verified"] + get_expected_version_labels() + required = ["verified", *required_labels] missing = [label for label in required if label not in labels_list] if not missing: return labels @@ -944,7 +968,9 @@ def format_test_status(text: str) -> str: return f'{text}' -def format_results_as_html_table(results) -> str: +def format_results_as_html_table( + results, *, required_pr_labels: tuple[str, ...] = () +) -> str: if not isinstance(results, pd.DataFrame): return results @@ -977,7 +1003,9 @@ def format_col_name(col_name: str) -> str: "PR Number": lambda n: url_to_html_link( f"https://github.com/{GITHUB_REPO}/pull/{n}" ), - "PR Labels": format_pr_labels_with_verification, + "PR Labels": lambda labels: format_pr_labels_with_verification( + labels, required_pr_labels + ), } html = results.to_html( @@ -1276,7 +1304,10 @@ def create_workflow_report( "

PR details are not loaded during preview.

" if mark_preview or pr_number != 0 else format_results_as_html_table( - results_dfs["prs_in_release"] + results_dfs["prs_in_release"], + required_pr_labels=get_expected_version_labels( + pr_number, commit_sha, branch_name + ), ) ), "ci_jobs_status_html": format_results_as_html_table( diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index f42c1cf56e74..3955c93f8096 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -6335,5 +6335,4 @@ jobs: if: ${{ !cancelled() }} uses: ./.github/actions/create_workflow_report with: - workflow_config: ${{ toJson(needs) }} final: true diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index f130e38a4cad..a1ad9b881c06 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -5807,5 +5807,4 @@ jobs: if: ${{ !cancelled() }} uses: ./.github/actions/create_workflow_report with: - workflow_config: ${{ toJson(needs) }} final: true diff --git a/.github/workflows/release_builds.yml b/.github/workflows/release_builds.yml index 2722350cf997..d5638ba27e93 100644 --- a/.github/workflows/release_builds.yml +++ b/.github/workflows/release_builds.yml @@ -1347,5 +1347,4 @@ jobs: if: ${{ !cancelled() }} uses: ./.github/actions/create_workflow_report with: - workflow_config: ${{ toJson(needs) }} final: true diff --git a/ci/praktika/yaml_additional_templates.py b/ci/praktika/yaml_additional_templates.py index 200e75495dea..774b7a5f6aa2 100644 --- a/ci/praktika/yaml_additional_templates.py +++ b/ci/praktika/yaml_additional_templates.py @@ -101,7 +101,6 @@ class AltinityWorkflowTemplates: if: ${{ !cancelled() }} uses: ./.github/actions/create_workflow_report with: - workflow_config: ${{ toJson(needs) }} final: true """, } From 2b9050e8e00076e148d64a40417ed39c05b2d357 Mon Sep 17 00:00:00 2001 From: altinity-robot Date: Mon, 27 Jul 2026 09:29:35 -0400 Subject: [PATCH 5/5] simplified report version check, full version now only checked for tags --- .../create_workflow_report.py | 102 +++++++++--------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/.github/actions/create_workflow_report/create_workflow_report.py b/.github/actions/create_workflow_report/create_workflow_report.py index 8bb928c5b51a..5dd7c7bc664c 100755 --- a/.github/actions/create_workflow_report/create_workflow_report.py +++ b/.github/actions/create_workflow_report/create_workflow_report.py @@ -417,16 +417,36 @@ def get_prs_in_release_dataframe( return _enrich_prs_in_release_merge_prs(df, repo), baseline_date +def _parse_release_ref(ref_name: str) -> tuple[str, str, str | None] | None: + """Parse a release ref into (flavour, minor_version, full_version). + + Handles version tags (``v25.8.28.10001.altinitystable``) and release + branches (``stable-25.8``). ``full_version`` is None for branches, which + carry no patch or tweak. Returns None for refs without a version. + """ + # Match tag + match = re.match( + r"^v((\d+\.\d+)\.\d+\.\d+)\.altinity(stable|antalya|test)$", ref_name + ) + if match: + flavour = "antalya" if match.group(3) == "antalya" else "stable" + return flavour, match.group(2), match.group(1) + # Match branch + match = re.match(r"^(stable|antalya)-(\d+\.\d+)$", ref_name) + if match: + return match.group(1), match.group(2), None + return None + + def _prs_in_release_search_url(branch_name: str, baseline_date: str | None) -> str | None: if not baseline_date: return None # Tag-triggered runs expose the tag as head_branch; search needs the PR base # branch (e.g. v25.8.28.10001.altinitystable → stable-25.8). - match = re.match( - r"^v(\d+)\.(\d+)\.\d+\.\d+\.altinity(stable|antalya)$", branch_name - ) - if match: - branch_name = f"{match.group(3)}-{match.group(1)}.{match.group(2)}" + parsed = _parse_release_ref(branch_name) + if parsed: + flavour, minor_version, _ = parsed + branch_name = f"{flavour}-{minor_version}" query = f"is:pr base:{branch_name} merged:>{baseline_date}" return ( f"https://github.com/{GITHUB_REPO}/pulls?" @@ -775,43 +795,29 @@ def get_cached_job( workflow_config = get_workflow_config(pr_number, commit_sha, ref_name) return workflow_config.get("cache_jobs", {}).get(job_name, {}) - @lru_cache -def get_expected_version_labels( - pr_number: int, commit_sha: str, ref_name: str -) -> tuple[str, ...]: - """Return required version labels from workflow_config.custom_data.version. - - Stable releases use the minor and full version labels. Antalya releases use - ``antalya``, ``antalya-{major}.{minor}``, and ``antalya-{full_version}``. - Returns an empty tuple when version metadata is unavailable. +def get_expected_version_labels(ref_name: str) -> tuple[str, ...]: + """Return required version labels for the release ref being reported. + + Stable releases use the minor version label. Antalya releases use + ``antalya`` and ``antalya-{minor_version}``. The full version label is only + required on tag-triggered runs, where the tag pins patch and tweak. + Returns an empty tuple when the ref carries no version. """ - try: - version = ( - get_workflow_config(pr_number, commit_sha, ref_name) - .get("custom_data", {}) - .get("version") - or {} - ) - major = version.get("major") - minor = version.get("minor") - patch = version.get("patch") - tweak = version.get("tweak") - flavour = version.get("flavour") - if None in (major, minor, patch, tweak, flavour): - print("WARNING:Incomplete version metadata in workflow config.") - return () - minor_version = f"{major}.{minor}" - full_version = f"{minor_version}.{patch}.{tweak}" - if flavour == "altinityantalya": - return ("antalya", f"antalya-{minor_version}", f"antalya-{full_version}") - if flavour in ["altinitystable", "altinitytest"]: - return (minor_version, full_version) - print(f"WARNING:Unknown release flavour in workflow config: {flavour}") - return () - except Exception as e: - print(f"WARNING:Could not read expected version labels: {e}") + parsed = _parse_release_ref(ref_name) + if not parsed: + print(f"WARNING:No version in ref [{ref_name}], not checking version labels.") return () + flavour, minor_version, full_version = parsed + if flavour == "antalya": + labels = ("antalya", f"antalya-{minor_version}") + if full_version: + labels += (f"antalya-{full_version}",) + return labels + labels = (minor_version,) + if full_version: + labels += (full_version,) + return labels def get_cves(pr_number, commit_sha, branch): @@ -926,17 +932,15 @@ def url_to_html_link(url: str) -> str: return f'{text}' -def format_pr_labels_with_verification( - labels: str, required_labels: tuple[str, ...] = () -) -> str: +def format_pr_labels_with_verification(labels: str, branch_name: str = "") -> str: """Format the PR labels with verification. Expects ``labels`` to be already HTML-escaped at collection time (see _enrich_prs_in_release_merge_prs). - Requires ``verified`` plus ``required_labels`` (the expected version labels - when version metadata is available). + Requires ``verified`` plus the version labels expected for ``branch_name``. """ + required_labels = get_expected_version_labels(branch_name) if branch_name else () labels_list = labels.split(", ") if labels else [] required = ["verified", *required_labels] missing = [label for label in required if label not in labels_list] @@ -968,9 +972,7 @@ def format_test_status(text: str) -> str: return f'{text}' -def format_results_as_html_table( - results, *, required_pr_labels: tuple[str, ...] = () -) -> str: +def format_results_as_html_table(results, *, branch_name: str = "") -> str: if not isinstance(results, pd.DataFrame): return results @@ -1004,7 +1006,7 @@ def format_col_name(col_name: str) -> str: f"https://github.com/{GITHUB_REPO}/pull/{n}" ), "PR Labels": lambda labels: format_pr_labels_with_verification( - labels, required_pr_labels + labels, branch_name=branch_name ), } @@ -1305,9 +1307,7 @@ def create_workflow_report( if mark_preview or pr_number != 0 else format_results_as_html_table( results_dfs["prs_in_release"], - required_pr_labels=get_expected_version_labels( - pr_number, commit_sha, branch_name - ), + branch_name=branch_name, ) ), "ci_jobs_status_html": format_results_as_html_table(