|
| 1 | +name: SonarCloud Full Metrics (main branch) |
| 2 | + |
| 3 | +on: |
| 4 | + push: |
| 5 | + branches: [main, master, sonarqube] |
| 6 | + pull_request: |
| 7 | + branches: [main, master, sonarqube] |
| 8 | + workflow_dispatch: |
| 9 | + |
| 10 | +permissions: |
| 11 | + contents: read |
| 12 | + |
| 13 | +jobs: |
| 14 | + full-metrics: |
| 15 | + name: Full Project Report |
| 16 | + runs-on: ubuntu-latest |
| 17 | + |
| 18 | + steps: |
| 19 | + - name: Checkout |
| 20 | + uses: actions/checkout@v4 |
| 21 | + with: |
| 22 | + fetch-depth: 0 |
| 23 | + |
| 24 | + # ===================================================== |
| 25 | + # Backend-Tests + Coverage |
| 26 | + # ===================================================== |
| 27 | + - name: Set up Python |
| 28 | + uses: actions/setup-python@v5 |
| 29 | + with: |
| 30 | + python-version: "3.10" |
| 31 | + |
| 32 | + - name: Install backend deps |
| 33 | + working-directory: project/backend |
| 34 | + run: | |
| 35 | + python -m pip install --upgrade pip |
| 36 | + python -m pip install -r requirements.txt |
| 37 | + python -m pip install "pytest-cov>=5,<7" "coverage>=7" |
| 38 | +
|
| 39 | + - name: Run backend tests with coverage |
| 40 | + working-directory: project |
| 41 | + run: | |
| 42 | + python -m pytest \ |
| 43 | + --cov=backend \ |
| 44 | + --cov-report=xml:backend/coverage.xml \ |
| 45 | + backend/tests/ |
| 46 | + continue-on-error: true |
| 47 | + |
| 48 | + # ===================================================== |
| 49 | + # SonarCloud Scan |
| 50 | + # ===================================================== |
| 51 | + - name: SonarQube Scan |
| 52 | + uses: SonarSource/sonarqube-scan-action@v3 |
| 53 | + with: |
| 54 | + projectBaseDir: project |
| 55 | + args: > |
| 56 | + -Dsonar.projectKey=GalacticCodeGambit_LazyCook |
| 57 | + -Dsonar.organization=galacticcodegambit |
| 58 | + -Dproject.settings=sonar-project.properties |
| 59 | + -Dsonar.python.coverage.reportPaths=backend/coverage.xml |
| 60 | + -Dsonar.python.version=3.10 |
| 61 | + env: |
| 62 | + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} |
| 63 | + |
| 64 | + - name: Wait for analysis |
| 65 | + id: qg |
| 66 | + uses: SonarSource/sonarqube-quality-gate-action@master |
| 67 | + timeout-minutes: 5 |
| 68 | + continue-on-error: true |
| 69 | + with: |
| 70 | + scanMetadataReportFile: project/.scannerwork/report-task.txt |
| 71 | + env: |
| 72 | + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} |
| 73 | + |
| 74 | + # ===================================================== |
| 75 | + # Voller Projekt-Report ins Job-Summary |
| 76 | + # ===================================================== |
| 77 | + - name: Render Full Metrics |
| 78 | + if: always() |
| 79 | + env: |
| 80 | + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} |
| 81 | + SONAR_HOST: https://sonarcloud.io |
| 82 | + QG_STATUS: ${{ steps.qg.outputs.quality-gate-status }} |
| 83 | + run: | |
| 84 | + set +e |
| 85 | + KEY="GalacticCodeGambit_LazyCook" |
| 86 | + OUT="$GITHUB_STEP_SUMMARY" |
| 87 | +
|
| 88 | + # Alle relevanten Branch-Metriken auf einen Schwung holen |
| 89 | + METRICS="alert_status,bugs,vulnerabilities,code_smells,security_hotspots,security_hotspots_reviewed,coverage,line_coverage,branch_coverage,lines_to_cover,uncovered_lines,duplicated_lines_density,duplicated_blocks,duplicated_files,duplicated_lines,complexity,cognitive_complexity,classes,functions,statements,files,ncloc,comment_lines,comment_lines_density,sqale_rating,reliability_rating,security_rating,security_review_rating,sqale_index,sqale_debt_ratio,reliability_remediation_effort,security_remediation_effort" |
| 90 | +
|
| 91 | + URL="${SONAR_HOST}/api/measures/component?component=${KEY}&metricKeys=${METRICS}" |
| 92 | + RESP=$(curl -s -u "${SONAR_TOKEN}:" "${URL}") |
| 93 | +
|
| 94 | + # Mini-Diagnose im Log |
| 95 | + echo "Anfrage: ${URL}" |
| 96 | + echo "Response (erste 300 Zeichen):" |
| 97 | + echo "${RESP}" | head -c 300 |
| 98 | + echo "" |
| 99 | +
|
| 100 | + # Hilfsfunktion: Metrik-Wert holen, "-" als Fallback |
| 101 | + get() { |
| 102 | + echo "${RESP}" | jq -r --arg k "$1" \ |
| 103 | + '(.component.measures[]? | select(.metric==$k) | .value) // "-"' 2>/dev/null |
| 104 | + } |
| 105 | +
|
| 106 | + # Rating-Buchstabe (1.0..5.0 → A..E) |
| 107 | + rating() { |
| 108 | + case "$(get "$1")" in |
| 109 | + 1.0) echo "A" ;; 2.0) echo "B" ;; 3.0) echo "C" ;; |
| 110 | + 4.0) echo "D" ;; 5.0) echo "E" ;; *) echo "-" ;; |
| 111 | + esac |
| 112 | + } |
| 113 | +
|
| 114 | + # Minuten → Stunden + Minuten |
| 115 | + fmt_debt() { |
| 116 | + local m=$1 |
| 117 | + if [[ "$m" =~ ^[0-9]+$ ]]; then |
| 118 | + local h=$((m / 60)); local r=$((m % 60)) |
| 119 | + echo "${h}h ${r}min" |
| 120 | + else |
| 121 | + echo "-" |
| 122 | + fi |
| 123 | + } |
| 124 | +
|
| 125 | + # Quality-Gate-Icon |
| 126 | + if [ "${QG_STATUS}" = "PASSED" ] || [ "$(get alert_status)" = "OK" ]; then |
| 127 | + QG_ICON=":white_check_mark:"; QG_TXT="PASSED" |
| 128 | + else |
| 129 | + QG_ICON=":x:"; QG_TXT="FAILED" |
| 130 | + fi |
| 131 | +
|
| 132 | + DEBT_FMT=$(fmt_debt "$(get sqale_index)") |
| 133 | + REL_EFF_FMT=$(fmt_debt "$(get reliability_remediation_effort)") |
| 134 | + SEC_EFF_FMT=$(fmt_debt "$(get security_remediation_effort)") |
| 135 | +
|
| 136 | + # ===== Summary schreiben ===== |
| 137 | + { |
| 138 | + echo "# SonarCloud – Voller Projekt-Report" |
| 139 | + echo "" |
| 140 | + echo "**Quality Gate:** ${QG_ICON} **${QG_TXT}** " |
| 141 | + echo "**Branch:** ${GITHUB_REF_NAME} " |
| 142 | + echo "**Commit:** ${GITHUB_SHA:0:7} " |
| 143 | + echo "**Dashboard:** [${SONAR_HOST}/project/overview?id=${KEY}](${SONAR_HOST}/project/overview?id=${KEY})" |
| 144 | + echo "" |
| 145 | +
|
| 146 | + # --- Übersicht --- |
| 147 | + echo "## Projekt-Übersicht" |
| 148 | + echo "" |
| 149 | + echo "| Kennzahl | Wert |" |
| 150 | + echo "|---|---:|" |
| 151 | + echo "| Lines of Code | $(get ncloc) |" |
| 152 | + echo "| Kommentar-Zeilen | $(get comment_lines) |" |
| 153 | + echo "| Kommentar-Anteil | $(get comment_lines_density)% |" |
| 154 | + echo "| Dateien | $(get files) |" |
| 155 | + echo "| Klassen | $(get classes) |" |
| 156 | + echo "| Funktionen | $(get functions) |" |
| 157 | + echo "| Statements | $(get statements) |" |
| 158 | + echo "" |
| 159 | +
|
| 160 | + # --- Coverage --- |
| 161 | + echo "## Test-Coverage" |
| 162 | + echo "" |
| 163 | + echo "| Metrik | Wert |" |
| 164 | + echo "|---|---:|" |
| 165 | + echo "| Coverage (gesamt) | $(get coverage)% |" |
| 166 | + echo "| Line Coverage | $(get line_coverage)% |" |
| 167 | + echo "| Branch Coverage | $(get branch_coverage)% |" |
| 168 | + echo "| Deckbare Zeilen | $(get lines_to_cover) |" |
| 169 | + echo "| Ungedeckte Zeilen | $(get uncovered_lines) |" |
| 170 | + echo "" |
| 171 | +
|
| 172 | + # --- Duplikate --- |
| 173 | + echo "## Duplikate" |
| 174 | + echo "" |
| 175 | + echo "| Metrik | Wert |" |
| 176 | + echo "|---|---:|" |
| 177 | + echo "| Duplizierte Zeilen (Rate) | $(get duplicated_lines_density)% |" |
| 178 | + echo "| Duplizierte Zeilen (absolut) | $(get duplicated_lines) |" |
| 179 | + echo "| Duplikat-Blöcke | $(get duplicated_blocks) |" |
| 180 | + echo "| Betroffene Dateien | $(get duplicated_files) |" |
| 181 | + echo "" |
| 182 | +
|
| 183 | + # --- Komplexität --- |
| 184 | + echo "## Komplexität" |
| 185 | + echo "" |
| 186 | + echo "| Metrik | Wert |" |
| 187 | + echo "|---|---:|" |
| 188 | + echo "| Zyklomatische Komplexität | $(get complexity) |" |
| 189 | + echo "| Cognitive Complexity | $(get cognitive_complexity) |" |
| 190 | + echo "" |
| 191 | +
|
| 192 | + # --- Issues & Ratings --- |
| 193 | + echo "## Issues & Bewertungen" |
| 194 | + echo "" |
| 195 | + echo "| Kategorie | Rating | Anzahl | Behebungs-Aufwand |" |
| 196 | + echo "|---|:---:|---:|---:|" |
| 197 | + echo "| Reliability (Bugs) | $(rating reliability_rating) | $(get bugs) | ${REL_EFF_FMT} |" |
| 198 | + echo "| Security (Vulnerabilities) | $(rating security_rating) | $(get vulnerabilities) | ${SEC_EFF_FMT} |" |
| 199 | + echo "| Maintainability (Code Smells) | $(rating sqale_rating) | $(get code_smells) | ${DEBT_FMT} |" |
| 200 | + echo "| Security Hotspots | $(rating security_review_rating) | $(get security_hotspots) | $(get security_hotspots_reviewed)% reviewed |" |
| 201 | + echo "" |
| 202 | +
|
| 203 | + # --- Technische Schuld --- |
| 204 | + echo "## Technische Schuld" |
| 205 | + echo "" |
| 206 | + echo "| Metrik | Wert |" |
| 207 | + echo "|---|---:|" |
| 208 | + echo "| Gesamte Schuld | ${DEBT_FMT} |" |
| 209 | + echo "| Debt Ratio | $(get sqale_debt_ratio)% |" |
| 210 | + echo "" |
| 211 | + } >> "$OUT" |
| 212 | +
|
| 213 | + # ===================================================== |
| 214 | + # Top-10 Files: höchste Komplexität, schlechteste Coverage |
| 215 | + # ===================================================== |
| 216 | + { |
| 217 | + echo "## Top-10 komplexeste Dateien" |
| 218 | + echo "" |
| 219 | + echo "| Datei | Komplexität | Cognitive | LoC |" |
| 220 | + echo "|---|---:|---:|---:|" |
| 221 | + } >> "$OUT" |
| 222 | +
|
| 223 | + curl -s -u "${SONAR_TOKEN}:" \ |
| 224 | + "${SONAR_HOST}/api/measures/component_tree?component=${KEY}&metricKeys=complexity,cognitive_complexity,ncloc&qualifiers=FIL&ps=10&s=metric&metricSort=complexity&asc=false" \ |
| 225 | + | jq -r '.components[]? |
| 226 | + | (.path // .key) as $p |
| 227 | + | ((.measures[]? | select(.metric=="complexity") | .value) // "-") as $c |
| 228 | + | ((.measures[]? | select(.metric=="cognitive_complexity")| .value) // "-") as $cog |
| 229 | + | ((.measures[]? | select(.metric=="ncloc") | .value) // "-") as $n |
| 230 | + | "| " + $p + " | " + $c + " | " + $cog + " | " + $n + " |"' \ |
| 231 | + >> "$OUT" |
| 232 | +
|
| 233 | + { |
| 234 | + echo "" |
| 235 | + echo "## Top-10 Dateien mit schlechtester Coverage" |
| 236 | + echo "" |
| 237 | + echo "| Datei | Coverage | Ungedeckte Zeilen | LoC |" |
| 238 | + echo "|---|---:|---:|---:|" |
| 239 | + } >> "$OUT" |
| 240 | +
|
| 241 | + curl -s -u "${SONAR_TOKEN}:" \ |
| 242 | + "${SONAR_HOST}/api/measures/component_tree?component=${KEY}&metricKeys=coverage,uncovered_lines,ncloc&qualifiers=FIL&ps=10&s=metric&metricSort=coverage&asc=true" \ |
| 243 | + | jq -r '.components[]? |
| 244 | + | select(.measures[]? | select(.metric=="coverage")) |
| 245 | + | (.path // .key) as $p |
| 246 | + | ((.measures[]? | select(.metric=="coverage") | .value) // "-") as $cov |
| 247 | + | ((.measures[]? | select(.metric=="uncovered_lines") | .value) // "-") as $unc |
| 248 | + | ((.measures[]? | select(.metric=="ncloc") | .value) // "-") as $n |
| 249 | + | "| " + $p + " | " + $cov + "% | " + $unc + " | " + $n + " |"' \ |
| 250 | + >> "$OUT" |
| 251 | +
|
| 252 | + { |
| 253 | + echo "" |
| 254 | + echo "## Top-10 Dateien mit den meisten Duplikat-Blöcken" |
| 255 | + echo "" |
| 256 | + echo "| Datei | Blöcke | Dupl. Zeilen | LoC |" |
| 257 | + echo "|---|---:|---:|---:|" |
| 258 | + } >> "$OUT" |
| 259 | +
|
| 260 | + curl -s -u "${SONAR_TOKEN}:" \ |
| 261 | + "${SONAR_HOST}/api/measures/component_tree?component=${KEY}&metricKeys=duplicated_blocks,duplicated_lines,ncloc&qualifiers=FIL&ps=10&s=metric&metricSort=duplicated_blocks&asc=false" \ |
| 262 | + | jq -r '.components[]? |
| 263 | + | (.path // .key) as $p |
| 264 | + | ((.measures[]? | select(.metric=="duplicated_blocks") | .value) // "0") as $b |
| 265 | + | ((.measures[]? | select(.metric=="duplicated_lines") | .value) // "0") as $l |
| 266 | + | ((.measures[]? | select(.metric=="ncloc") | .value) // "-") as $n |
| 267 | + | select(($b | tonumber) > 0) |
| 268 | + | "| " + $p + " | " + $b + " | " + $l + " | " + $n + " |"' \ |
| 269 | + >> "$OUT" |
| 270 | +
|
| 271 | + { |
| 272 | + echo "" |
| 273 | + echo "---" |
| 274 | + echo "_Generiert von SonarCloud-Analyse aus Push auf \`${GITHUB_REF_NAME}\`._" |
| 275 | + } >> "$OUT" |
0 commit comments