diff --git a/.github/actions/post-coverage-comment/action.yml b/.github/actions/post-coverage-comment/action.yml new file mode 100644 index 00000000..0f3a862a --- /dev/null +++ b/.github/actions/post-coverage-comment/action.yml @@ -0,0 +1,105 @@ +name: Post Coverage Comment +description: Posts a standardized code coverage comment on a pull request + +inputs: + pr_number: + description: 'Pull request number' + required: true + coverage_percentage: + description: 'Overall coverage percentage' + required: true + covered_lines: + description: 'Number of covered lines' + required: true + total_lines: + description: 'Total number of lines' + required: true + patch_coverage_pct: + description: 'Patch/diff coverage percentage' + required: true + low_coverage_files: + description: 'Files with lowest coverage (multiline)' + required: true + patch_coverage_summary: + description: 'Patch coverage summary markdown (multiline)' + required: true + ado_url: + description: 'Azure DevOps build URL' + required: true + +runs: + using: composite + steps: + - name: Post coverage comment + uses: marocchino/sticky-pull-request-comment@v2 + with: + header: Code Coverage Report + number: ${{ inputs.pr_number }} + message: | + # 📊 Code Coverage Report + + + + + + + +
+ + ### 🔥 Diff Coverage + ### **${{ inputs.patch_coverage_pct }}** +
+
+ + ### 🎯 Overall Coverage + ### **${{ inputs.coverage_percentage }}** +
+
+ + **📈 Total Lines Covered:** `${{ inputs.covered_lines }}` out of `${{ inputs.total_lines }}` + **📁 Project:** `mssql-python` + +
+ + --- + + ${{ inputs.patch_coverage_summary }} + + --- + ### 📋 Files Needing Attention + +
+ 📉 Files with overall lowest coverage (click to expand) +
+ + ```diff + ${{ inputs.low_coverage_files }} + ``` + +
+ + --- + ### 🔗 Quick Links + + + + + + + + + + +
+ ⚙️ Build Summary + + 📋 Coverage Details +
+ + [View Azure DevOps Build](${{ inputs.ado_url }}) + + + + [Browse Full Coverage Report](${{ inputs.ado_url }}&view=codecoverage-tab) + +
diff --git a/.github/workflows/forked-pr-coverage.yml b/.github/workflows/forked-pr-coverage.yml new file mode 100644 index 00000000..e616e884 --- /dev/null +++ b/.github/workflows/forked-pr-coverage.yml @@ -0,0 +1,111 @@ +name: Post Coverage Comment + +# This workflow handles posting coverage comments for FORKED PRs. +# +# Why a separate workflow? +# - Forked PRs have restricted GITHUB_TOKEN permissions for security +# - They cannot write comments directly to the base repository's PRs +# - workflow_run triggers run in the BASE repository context with full permissions +# - This allows us to safely post comments on forked PRs +# +# How it works: +# 1. PR Code Coverage workflow uploads coverage data as an artifact (forked PRs only) +# 2. This workflow triggers when PR Code Coverage completes successfully +# 3. Downloads the artifact and posts the comment with full write permissions +# +# Same-repo PRs post comments directly in pr-code-coverage.yml (faster) +# Forked PRs use this workflow (required for permissions) + +on: + workflow_run: + workflows: ["PR Code Coverage"] + types: + - completed + +jobs: + post-comment: + runs-on: ubuntu-latest + if: > + github.event.workflow_run.event == 'pull_request' && + github.event.workflow_run.conclusion == 'success' + permissions: + pull-requests: write + contents: read + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Download coverage data + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Download artifact with error handling for non-existent artifacts + if ! gh run download ${{ github.event.workflow_run.id }} \ + --repo ${{ github.repository }} \ + --name coverage-comment-data 2>&1; then + echo "⚠️ No coverage-comment-data artifact found" + echo "This is expected for same-repo PRs (they post comments directly)" + echo "Exiting gracefully..." + exit 0 + fi + + # Verify artifact was downloaded + if [[ ! -f pr-info.json ]]; then + echo "⚠️ Artifact downloaded but pr-info.json not found" + echo "This may indicate an issue with artifact upload" + exit 1 + fi + + - name: Read coverage data + id: coverage + run: | + if [[ ! -f pr-info.json ]]; then + echo "❌ pr-info.json not found" + exit 1 + fi + + cat pr-info.json + + # Extract values from JSON with proper quoting + PR_NUMBER="$(jq -r '.pr_number' pr-info.json)" + COVERAGE_PCT="$(jq -r '.coverage_percentage' pr-info.json)" + COVERED_LINES="$(jq -r '.covered_lines' pr-info.json)" + TOTAL_LINES="$(jq -r '.total_lines' pr-info.json)" + PATCH_PCT="$(jq -r '.patch_coverage_pct' pr-info.json)" + LOW_COV_FILES="$(jq -r '.low_coverage_files' pr-info.json)" + PATCH_SUMMARY="$(jq -r '.patch_coverage_summary' pr-info.json)" + ADO_URL="$(jq -r '.ado_url' pr-info.json)" + + # Export to env for next step (single-line values) + echo "PR_NUMBER=${PR_NUMBER}" >> $GITHUB_ENV + echo "COVERAGE_PERCENTAGE=${COVERAGE_PCT}" >> $GITHUB_ENV + echo "COVERED_LINES=${COVERED_LINES}" >> $GITHUB_ENV + echo "TOTAL_LINES=${TOTAL_LINES}" >> $GITHUB_ENV + echo "PATCH_COVERAGE_PCT=${PATCH_PCT}" >> $GITHUB_ENV + echo "ADO_URL=${ADO_URL}" >> $GITHUB_ENV + + # Handle multiline values with proper quoting + { + echo "LOW_COVERAGE_FILES<> $GITHUB_ENV + + { + echo "PATCH_COVERAGE_SUMMARY<> $GITHUB_ENV + + - name: Comment coverage summary on PR + uses: ./.github/actions/post-coverage-comment + with: + pr_number: ${{ env.PR_NUMBER }} + coverage_percentage: ${{ env.COVERAGE_PERCENTAGE }} + covered_lines: ${{ env.COVERED_LINES }} + total_lines: ${{ env.TOTAL_LINES }} + patch_coverage_pct: ${{ env.PATCH_COVERAGE_PCT }} + low_coverage_files: ${{ env.LOW_COVERAGE_FILES }} + patch_coverage_summary: ${{ env.PATCH_COVERAGE_SUMMARY }} + ado_url: ${{ env.ADO_URL }} diff --git a/.github/workflows/pr-code-coverage.yml b/.github/workflows/pr-code-coverage.yml index d00666b9..f2f1aad9 100644 --- a/.github/workflows/pr-code-coverage.yml +++ b/.github/workflows/pr-code-coverage.yml @@ -417,75 +417,59 @@ jobs: echo "PATCH_COVERAGE_SUMMARY=Patch coverage report could not be generated." >> $GITHUB_ENV fi - - name: Comment coverage summary on PR - uses: marocchino/sticky-pull-request-comment@v2 - with: - header: Code Coverage Report - message: | - # 📊 Code Coverage Report - - - - - - - -
- - ### 🔥 Diff Coverage - ### **${{ env.PATCH_COVERAGE_PCT }}** -
-
- - ### 🎯 Overall Coverage - ### **${{ env.COVERAGE_PERCENTAGE }}** -
-
- - **📈 Total Lines Covered:** `${{ env.COVERED_LINES }}` out of `${{ env.TOTAL_LINES }}` - **📁 Project:** `mssql-python` - -
- - --- - - ${{ env.PATCH_COVERAGE_SUMMARY }} - - --- - ### 📋 Files Needing Attention - -
- 📉 Files with overall lowest coverage (click to expand) -
- - ```diff - ${{ env.LOW_COVERAGE_FILES }} - ``` - -
- - --- - ### 🔗 Quick Links - - - - - - - - - - -
- ⚙️ Build Summary - - 📋 Coverage Details -
- - [View Azure DevOps Build](${{ env.ADO_URL }}) - - + - name: Save coverage data for comment + run: | + mkdir -p coverage-comment-data + jq -n \ + --arg pr_number "${{ github.event.pull_request.number }}" \ + --arg coverage_percentage "${{ env.COVERAGE_PERCENTAGE }}" \ + --arg covered_lines "${{ env.COVERED_LINES }}" \ + --arg total_lines "${{ env.TOTAL_LINES }}" \ + --arg patch_coverage_pct "${{ env.PATCH_COVERAGE_PCT }}" \ + --arg low_coverage_files "${{ env.LOW_COVERAGE_FILES }}" \ + --arg patch_coverage_summary "${{ env.PATCH_COVERAGE_SUMMARY }}" \ + --arg ado_url "${{ env.ADO_URL }}" \ + '{ + pr_number: $pr_number, + coverage_percentage: $coverage_percentage, + covered_lines: $covered_lines, + total_lines: $total_lines, + patch_coverage_pct: $patch_coverage_pct, + low_coverage_files: $low_coverage_files, + patch_coverage_summary: $patch_coverage_summary, + ado_url: $ado_url + }' > coverage-comment-data/pr-info.json + + # Validate JSON before uploading + echo "Validating generated JSON..." + jq . coverage-comment-data/pr-info.json > /dev/null || { + echo "❌ Invalid JSON generated" + cat coverage-comment-data/pr-info.json + exit 1 + } + echo "✅ JSON validation successful" + cat coverage-comment-data/pr-info.json - [Browse Full Coverage Report](${{ env.ADO_URL }}&view=codecoverage-tab) + - name: Upload coverage comment data + # Only upload artifact for forked PRs since same-repo PRs post comment directly + # This prevents unnecessary workflow_run triggers for same-repo PRs + if: github.event.pull_request.head.repo.full_name != github.repository + uses: actions/upload-artifact@v4 + with: + name: coverage-comment-data + path: coverage-comment-data/ + retention-days: 7 -
\ No newline at end of file + - name: Comment coverage summary on PR + # Skip for forked PRs due to token permission restrictions + if: github.event.pull_request.head.repo.full_name == github.repository + uses: ./.github/actions/post-coverage-comment + with: + pr_number: ${{ github.event.pull_request.number }} + coverage_percentage: ${{ env.COVERAGE_PERCENTAGE }} + covered_lines: ${{ env.COVERED_LINES }} + total_lines: ${{ env.TOTAL_LINES }} + patch_coverage_pct: ${{ env.PATCH_COVERAGE_PCT }} + low_coverage_files: ${{ env.LOW_COVERAGE_FILES }} + patch_coverage_summary: ${{ env.PATCH_COVERAGE_SUMMARY }} + ado_url: ${{ env.ADO_URL }} \ No newline at end of file