Skip to content

feat(green-score): rendering #2

feat(green-score): rendering

feat(green-score): rendering #2

Workflow file for this run

name: "🌿 Green API Score CI"
on:
push:
branches: [main, master, develop, feat/devoxx]
pull_request:
branches: [main, master]
permissions:
contents: read
pull-requests: write
pages: write
id-token: write
concurrency:
group: pr-green-api-${{ github.ref }}
cancel-in-progress: true
env:
JAVA_VERSION: '17'
jobs:
build:
name: "🔨 Build"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup JDK ${{ env.JAVA_VERSION }}
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: ${{ env.JAVA_VERSION }}
cache: maven
- name: Build baseline
run: mvn -q -DskipTests -f green-api-baseline/pom.xml package
- name: Build optimized
run: mvn -q -DskipTests -f green-api-optimized/pom.xml package
- name: Upload jars
uses: actions/upload-artifact@v4
with:
name: jars
path: |
green-api-baseline/target/*.jar
green-api-optimized/target/*.jar
green-score:
name: "🌿 Green Score Analysis"
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup JDK ${{ env.JAVA_VERSION }}
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: ${{ env.JAVA_VERSION }}
- name: Download jars
uses: actions/download-artifact@v4
with:
name: jars
- name: Start baseline (8080)
run: |
nohup java -jar green-api-baseline/target/green-api-baseline-0.0.1-SNAPSHOT.jar > baseline.log 2>&1 &
echo $! > baseline.pid
- name: Start optimized (8081)
run: |
nohup java -jar green-api-optimized/target/green-api-optimized-0.0.1-SNAPSHOT.jar > optimized.log 2>&1 &
echo $! > optimized.pid
- name: Wait for services
run: |
for i in $(seq 1 60); do
if curl -sSf http://localhost:8080/actuator/health >/dev/null 2>&1 && \
curl -sSf http://localhost:8081/actuator/health >/dev/null 2>&1; then
echo "✅ Both services are up"; break
fi
sleep 2
done
- name: Run Green Score Analyzer
run: |
chmod +x scripts/green-score-analyzer.sh
chmod +x scripts/green-score-analyzer_withdiscovery.sh
BASELINE_PORT=8080 OPTIMIZED_PORT=8081 bash scripts/green-score-analyzer_withdiscovery.sh
- name: Generate Green Score badge
run: |
chmod +x scripts/generate-badge.sh
bash scripts/generate-badge.sh reports/latest-report.json badges/green-score.svg || true
- name: Generate Dashboard (HTML + Markdown)
run: |
chmod +x scripts/generate-dashboard.sh
bash scripts/generate-dashboard.sh reports/latest-report.json dashboard/index.save.html dashboard/index.html || true
- name: Assertions — payload & 304 & Green Score
shell: bash
run: |
set -e
REPORT="reports/latest-report.json"
# Vérifier que le rapport existe et n'est pas vide
if [ ! -f "$REPORT" ]; then
echo "❌ ERROR: Report file not found: $REPORT"
echo "Listing reports/ directory:"
ls -la reports/ || true
exit 1
fi
if [ ! -s "$REPORT" ]; then
echo "❌ ERROR: Report file is empty: $REPORT"
echo "The analyzer script may have failed silently."
echo "Check the analyzer logs above for errors."
exit 1
fi
echo "=== Green Score Report ==="
python3 -m json.tool "$REPORT"
TOTAL=$(python3 -c "import json;print(json.load(open('$REPORT'))['green_score']['total'])")
GRADE=$(python3 -c "import json;print(json.load(open('$REPORT'))['green_score']['grade'])")
echo "## 🌿 Green Score: ${TOTAL}/100 (Grade: ${GRADE})" >> $GITHUB_STEP_SUMMARY
# Assert pagination < baseline/10
B_SIZE=$(python3 -c "import json;r=json.load(open('$REPORT'));print(r['measurements']['baseline']['full_payload']['size_download'])")
O_SIZE=$(python3 -c "import json;r=json.load(open('$REPORT'));print(r['measurements']['optimized']['pagination']['size_download'])")
echo "Baseline: ${B_SIZE} bytes | Optimized paginated: ${O_SIZE} bytes" >> $GITHUB_STEP_SUMMARY
python3 -c "b=$B_SIZE; o=$O_SIZE; assert o < b / 10, f'FAIL: optimized ({o}) >= baseline/10 ({b/10})'; print('✅ Pagination assertion PASSED')"
# Assert 304
ETAG_CODE=$(python3 -c "import json;r=json.load(open('$REPORT'));print(r['measurements']['optimized']['etag_304']['http_code'])")
python3 -c "assert $ETAG_CODE == 304, f'FAIL: expected 304, got $ETAG_CODE'; print('✅ ETag 304 assertion PASSED')"
# Assert Green Score >= 50
python3 -c "assert $TOTAL >= 50, f'FAIL: Green Score {$TOTAL} < 50'; print('✅ Green Score >= 50 PASSED')"
# Regression gate (optional): green-score-threshold.json
if [ -f green-score-threshold.json ]; then
MIN_SCORE=$(python3 -c "import json;print(json.load(open('green-score-threshold.json'))['minScore'])")
echo "Min score threshold: ${MIN_SCORE}" >> $GITHUB_STEP_SUMMARY
python3 -c "total=$TOTAL; minimum=$MIN_SCORE; assert total >= minimum, f'FAIL: Green Score {total} < threshold {minimum}'; print('✅ Green Score threshold PASSED')"
fi
- name: Upload report
uses: actions/upload-artifact@v4
if: always()
with:
name: green-score-report
path: reports/
- name: Upload badge
uses: actions/upload-artifact@v4
if: always()
with:
name: green-score-badge
path: badges/
- name: Upload dashboard
uses: actions/upload-artifact@v4
if: always()
with:
name: green-score-dashboard
path: |
dashboard/index.html
dashboard/index.md
- name: Comment PR with Green Score
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const report = JSON.parse(fs.readFileSync('reports/latest-report.json', 'utf8'));
const gs = report.green_score;
const emoji = gs.total >= 80 ? '🟢' : gs.total >= 50 ? '🟡' : '🔴';
let body = `## ${emoji} Green API Score: **${gs.total}/100** (Grade: **${gs.grade}**)\n\n`;
body += `| Règle | Score | Max |\n|---|---|---|\n`;
const labels = {
'DE11_pagination': 'DE11 Pagination',
'DE08_fields': 'DE08 Filtrage champs',
'DE01_compression': 'DE01 Compression',
'DE02_DE03_cache': 'DE02/03 Cache ETag',
'DE06_delta': 'DE06 Delta',
'range_206': '206 Range',
'LO01_observability': 'LO01 Observabilité',
'US07_rate_limit': 'US07 Rate Limit',
'AR02_format_cbor': 'AR02 CBOR',
};
const maxScores = {
DE11_pagination: 15, DE08_fields: 15, DE01_compression: 15,
DE02_DE03_cache: 15, DE06_delta: 10, range_206: 10,
LO01_observability: 5, US07_rate_limit: 5, AR02_format_cbor: 10
};
for (const [key, label] of Object.entries(labels)) {
const score = gs.breakdown[key] || 0;
const max = maxScores[key] || 0;
const icon = score >= max * 0.75 ? '✅' : score >= max * 0.4 ? '⚠️' : '❌';
body += `| ${icon} ${label} | ${score} | ${max} |\n`;
}
body += `\n📅 *${report.timestamp}*`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: body
});
- name: Upload logs (on failure)
if: failure()
uses: actions/upload-artifact@v4
with:
name: server-logs
path: |
baseline.log
optimized.log
- name: Stop services
if: always()
run: |
kill $(cat baseline.pid) 2>/dev/null || true
kill $(cat optimized.pid) 2>/dev/null || true
spectral-lint:
name: "🔬 Spectral OpenAPI Lint"
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup JDK ${{ env.JAVA_VERSION }}
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: ${{ env.JAVA_VERSION }}
- name: Download jars
uses: actions/download-artifact@v4
with:
name: jars
- name: Start optimized
run: |
nohup java -jar green-api-optimized/target/green-api-optimized-0.0.1-SNAPSHOT.jar > opt.log 2>&1 &
for i in $(seq 1 60); do
curl -sSf http://localhost:8081/actuator/health >/dev/null 2>&1 && break
sleep 2
done
- name: Fetch OpenAPI spec
run: curl -s http://localhost:8081/v3/api-docs -o openapi.json
- name: Install Spectral
run: npm install -g @stoplight/spectral-cli
- name: Run Spectral with Green API rules
run: spectral lint openapi.json --ruleset .spectral.yml --format stylish || true
auto-discover:
name: "🔍 Auto-Discover Analysis"
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup JDK ${{ env.JAVA_VERSION }}
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: ${{ env.JAVA_VERSION }}
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install Spectral
run: npm install -g @stoplight/spectral-cli
- name: Download jars
uses: actions/download-artifact@v4
with:
name: jars
- name: Start optimized API
run: |
nohup java -jar green-api-optimized/target/green-api-optimized-0.0.1-SNAPSHOT.jar > optimized.log 2>&1 &
echo $! > optimized.pid
for i in $(seq 1 60); do
curl -sSf http://localhost:8081/actuator/health >/dev/null 2>&1 && echo "API ready" && break
sleep 2
done
- name: Run Auto-Discover Analyzer
run: |
python3 scripts/green-api-auto-discover.py \
--target http://localhost:8081 \
--repeat 3
- name: Upload reports
uses: actions/upload-artifact@v4
if: always()
with:
name: auto-discover-report
path: reports/
- name: Stop services
if: always()
run: kill $(cat optimized.pid) 2>/dev/null || true
deploy-pages:
name: "🌐 Deploy Dashboard to GitHub Pages"
needs: green-score
if: github.event_name == 'push'
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- uses: actions/checkout@v4
- name: Download dashboard artifact
uses: actions/download-artifact@v4
with:
name: green-score-dashboard
path: dashboard/
- name: Download badge artifact
uses: actions/download-artifact@v4
with:
name: green-score-badge
path: badges/
- name: Download report artifact
uses: actions/download-artifact@v4
with:
name: green-score-report
path: reports/
- name: Prepare Pages content
run: |
mkdir -p _site
cp -r dashboard/* _site/
cp -r badges _site/badges
cp -r reports _site/reports
# .nojekyll ensures GitHub Pages serves HTML files as-is
touch _site/.nojekyll
- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v3
with:
path: _site
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4