Fix: align frontend SonarQube project name with backend pattern #162
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [ main ] | |
| pull_request: | |
| permissions: | |
| contents: read | |
| jobs: | |
| # --- 1. Backend -------------------------------------------------------------- | |
| backend: | |
| name: Backend (Maven) | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| actions: write | |
| checks: write | |
| steps: | |
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| - name: Set up JDK 25 | |
| uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 | |
| with: | |
| distribution: temurin | |
| java-version: 25 | |
| cache: maven | |
| - name: Run backend tests | |
| run: mvn -B -ntp -f backend/pom.xml verify | |
| - name: Upload backend JaCoCo reports | |
| if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 | |
| with: | |
| name: backend-jacoco-reports | |
| path: backend/**/target/site/jacoco/jacoco.xml | |
| if-no-files-found: warn | |
| retention-days: 7 | |
| - name: Upload backend test reports | |
| if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 | |
| with: | |
| name: backend-test-reports | |
| path: | | |
| backend/**/target/surefire-reports/TEST-*.xml | |
| backend/**/target/failsafe-reports/TEST-*.xml | |
| if-no-files-found: warn | |
| retention-days: 7 | |
| # --- 2. Frontend ------------------------------------------------------------- | |
| frontend: | |
| name: Frontend (Angular) | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| actions: write | |
| steps: | |
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| - name: Use Node 20 | |
| uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 | |
| with: | |
| node-version: 20 | |
| cache: npm | |
| cache-dependency-path: frontend/package-lock.json | |
| - name: Install dependencies | |
| working-directory: frontend | |
| run: npm ci --ignore-scripts | |
| - name: Lint | |
| working-directory: frontend | |
| run: npm run lint --if-present | |
| - name: Test | |
| working-directory: frontend | |
| run: npm run test:ci | |
| - name: Upload frontend coverage report | |
| if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 | |
| with: | |
| name: frontend-coverage | |
| path: frontend/coverage/autocomplete-frontend/lcov.info | |
| if-no-files-found: warn | |
| retention-days: 7 | |
| - name: Build | |
| working-directory: frontend | |
| run: npm run build --if-present | |
| # --- 3. SonarQube ------------------------------------------------------------ | |
| sonarqube: | |
| name: SonarQube - backend | |
| runs-on: ubuntu-latest | |
| needs: [backend] | |
| permissions: | |
| contents: read | |
| actions: write | |
| if: > | |
| (github.event_name == 'push' && github.ref == 'refs/heads/main') || | |
| (github.event_name == 'pull_request' && github.actor != 'dependabot[bot]') | |
| env: | |
| SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} | |
| SONAR_HOST_URL: ${{ vars.SONAR_HOST_URL }} | |
| SONAR_ORGANIZATION: ${{ vars.SONAR_ORGANIZATION }} | |
| steps: | |
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Check Sonar configuration | |
| id: sonar-check | |
| shell: bash | |
| run: | | |
| if [ -z "$SONAR_TOKEN" ]; then | |
| echo "sonar_enabled=false" >> "$GITHUB_OUTPUT" | |
| echo "SONAR_TOKEN is not set. Skipping backend SonarQube analysis." | |
| exit 0 | |
| fi | |
| host_url="${SONAR_HOST_URL:-https://sonarcloud.io}" | |
| if [ "$host_url" = "https://sonarcloud.io" ] && [ -z "$SONAR_ORGANIZATION" ]; then | |
| echo "sonar_enabled=false" >> "$GITHUB_OUTPUT" | |
| echo "SONAR_ORGANIZATION is required for SonarCloud. Skipping backend SonarQube analysis." | |
| exit 0 | |
| fi | |
| echo "sonar_enabled=true" >> "$GITHUB_OUTPUT" | |
| - name: Set up JDK 25 | |
| if: steps.sonar-check.outputs.sonar_enabled == 'true' | |
| uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 | |
| with: | |
| distribution: temurin | |
| java-version: 25 | |
| cache: maven | |
| - name: Cache SonarQube packages | |
| if: steps.sonar-check.outputs.sonar_enabled == 'true' | |
| uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 | |
| with: | |
| path: ~/.sonar/cache | |
| key: ${{ runner.os }}-sonar-backend | |
| restore-keys: ${{ runner.os }}-sonar- | |
| - name: Download backend coverage reports | |
| if: steps.sonar-check.outputs.sonar_enabled == 'true' | |
| continue-on-error: true | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 | |
| with: | |
| name: backend-jacoco-reports | |
| path: backend-coverage | |
| - name: SonarQube analysis (backend) | |
| if: steps.sonar-check.outputs.sonar_enabled == 'true' | |
| env: | |
| REPO_NAME: ${{ github.event.repository.name }} | |
| EVENT_NAME: ${{ github.event_name }} | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| PR_HEAD_REF: ${{ github.head_ref }} | |
| PR_BASE_REF: ${{ github.base_ref }} | |
| shell: bash | |
| run: | | |
| host_url="${SONAR_HOST_URL:-https://sonarcloud.io}" | |
| report_paths="" | |
| if [ -d "backend-coverage" ]; then | |
| report_paths=$(find "$(pwd)/backend-coverage" -type f -name "jacoco.xml" -print 2>/dev/null | paste -sd, - || true) | |
| fi | |
| cmd=( | |
| mvn -B -ntp | |
| -f backend/pom.xml | |
| -Djacoco.skip=true | |
| -DskipTests | |
| verify | |
| sonar:sonar | |
| "-Dsonar.host.url=${host_url}" | |
| "-Dsonar.projectKey=${REPO_NAME}-backend" | |
| "-Dsonar.projectName=${REPO_NAME}-backend" | |
| "-Dsonar.token=${SONAR_TOKEN}" | |
| ) | |
| if [ -n "${report_paths}" ]; then | |
| cmd+=("-Dsonar.coverage.jacoco.xmlReportPaths=${report_paths}") | |
| else | |
| echo "JaCoCo XML reports not found; Sonar will run without coverage input." | |
| fi | |
| if [ -n "${SONAR_ORGANIZATION}" ]; then | |
| cmd+=("-Dsonar.organization=${SONAR_ORGANIZATION}") | |
| fi | |
| if [ "${EVENT_NAME}" = "pull_request" ] && [ -n "${PR_NUMBER}" ] && [ -n "${PR_HEAD_REF}" ] && [ -n "${PR_BASE_REF}" ]; then | |
| cmd+=("-Dsonar.pullrequest.key=${PR_NUMBER}") | |
| cmd+=("-Dsonar.pullrequest.branch=${PR_HEAD_REF}") | |
| cmd+=("-Dsonar.pullrequest.base=${PR_BASE_REF}") | |
| fi | |
| "${cmd[@]}" | |
| - name: Locate backend Sonar report-task | |
| if: steps.sonar-check.outputs.sonar_enabled == 'true' | |
| id: report-task | |
| shell: bash | |
| run: | | |
| report_task=$(find backend -path "*/target/sonar/report-task.txt" -print | head -1) | |
| if [ -z "$report_task" ]; then | |
| echo "Unable to find backend Sonar report-task.txt" >&2 | |
| exit 1 | |
| fi | |
| echo "path=$report_task" >> "$GITHUB_OUTPUT" | |
| - name: Wait for Quality Gate and publish backend Sonar summary | |
| if: steps.sonar-check.outputs.sonar_enabled == 'true' | |
| shell: bash | |
| env: | |
| REPO_NAME: ${{ github.event.repository.name }} | |
| run: | | |
| extra_args=() | |
| if [ "${GITHUB_EVENT_NAME}" = "pull_request" ]; then | |
| extra_args+=("--allow-missing-new-code-metrics") | |
| fi | |
| python3 .github/scripts/sonar_quality_gate_report.py \ | |
| --component-name "backend" \ | |
| --project-key "${REPO_NAME}-backend" \ | |
| --report-task-file "${{ steps.report-task.outputs.path }}" \ | |
| --timeout-seconds 900 \ | |
| "${extra_args[@]}" | |
| # --- 4. SonarQube – frontend ------------------------------------------------- | |
| sonarqube-frontend: | |
| name: SonarQube - frontend | |
| runs-on: ubuntu-latest | |
| needs: [frontend] | |
| permissions: | |
| contents: read | |
| actions: read | |
| if: > | |
| (github.event_name == 'push' && github.ref == 'refs/heads/main') || | |
| (github.event_name == 'pull_request' && github.actor != 'dependabot[bot]') | |
| env: | |
| SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} | |
| SONAR_HOST_URL: ${{ vars.SONAR_HOST_URL }} | |
| SONAR_ORGANIZATION: ${{ vars.SONAR_ORGANIZATION }} | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true | |
| steps: | |
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Check Sonar configuration | |
| id: sonar-check | |
| shell: bash | |
| run: | | |
| if [ -z "$SONAR_TOKEN" ]; then | |
| echo "sonar_enabled=false" >> "$GITHUB_OUTPUT" | |
| echo "SONAR_TOKEN is not set. Skipping frontend SonarQube analysis." | |
| exit 0 | |
| fi | |
| host_url="${SONAR_HOST_URL:-https://sonarcloud.io}" | |
| if [ "$host_url" = "https://sonarcloud.io" ] && [ -z "$SONAR_ORGANIZATION" ]; then | |
| echo "sonar_enabled=false" >> "$GITHUB_OUTPUT" | |
| echo "SONAR_ORGANIZATION is required for SonarCloud. Skipping frontend SonarQube analysis." | |
| exit 0 | |
| fi | |
| echo "sonar_enabled=true" >> "$GITHUB_OUTPUT" | |
| - name: Download frontend coverage report | |
| if: steps.sonar-check.outputs.sonar_enabled == 'true' | |
| continue-on-error: true | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 | |
| with: | |
| name: frontend-coverage | |
| path: frontend/coverage | |
| - name: Ensure lcov.info is at expected path | |
| if: steps.sonar-check.outputs.sonar_enabled == 'true' | |
| shell: bash | |
| run: | | |
| expected="frontend/coverage/autocomplete-frontend/lcov.info" | |
| lcov=$(find frontend/coverage -name "lcov.info" 2>/dev/null | head -1) | |
| if [ -n "$lcov" ] && [ "$lcov" != "$expected" ]; then | |
| mkdir -p "$(dirname "$expected")" | |
| cp "$lcov" "$expected" | |
| echo "Reorganized lcov: $lcov -> $expected" | |
| fi | |
| if [ ! -f "$expected" ]; then | |
| echo "lcov.info not found; Sonar will run without coverage input." | |
| fi | |
| - name: SonarQube analysis (frontend) | |
| if: steps.sonar-check.outputs.sonar_enabled == 'true' | |
| uses: SonarSource/sonarqube-scan-action@7006c4492b2e0ee0f816d36501671557c97f5995 # v8 | |
| env: | |
| SONAR_HOST_URL: ${{ vars.SONAR_HOST_URL || 'https://sonarcloud.io' }} | |
| with: | |
| projectBaseDir: frontend | |
| args: > | |
| -Dsonar.projectKey=${{ github.event.repository.name }}-frontend | |
| -Dsonar.sources=src | |
| -Dsonar.tests=src | |
| -Dsonar.test.inclusions=**/*.spec.ts | |
| -Dsonar.javascript.lcov.reportPaths=coverage/autocomplete-frontend/lcov.info | |
| ${{ vars.SONAR_ORGANIZATION != '' && format('-Dsonar.organization={0}', vars.SONAR_ORGANIZATION) || '' }} | |
| - name: Wait for Quality Gate and publish frontend Sonar summary | |
| if: steps.sonar-check.outputs.sonar_enabled == 'true' | |
| shell: bash | |
| env: | |
| REPO_NAME: ${{ github.event.repository.name }} | |
| run: | | |
| report_task="frontend/.scannerwork/report-task.txt" | |
| if [ ! -f "$report_task" ]; then | |
| echo "Unable to find frontend Sonar report-task.txt at $report_task" >&2 | |
| exit 1 | |
| fi | |
| python3 .github/scripts/sonar_quality_gate_report.py \ | |
| --component-name "frontend" \ | |
| --project-key "${REPO_NAME}-frontend" \ | |
| --report-task-file "$report_task" \ | |
| --timeout-seconds 900 | |
| # --- 5. Docker build (no push on PRs) ---------------------------------------- | |
| docker: | |
| name: Docker Build | |
| runs-on: ubuntu-latest | |
| needs: [backend, frontend] | |
| permissions: | |
| contents: read | |
| actions: read | |
| steps: | |
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| # Java services need the repo root as build context (they COPY backend/) | |
| - name: Build autocomplete-service image | |
| run: docker build -f backend/autocomplete-service/Dockerfile -t autocomplete-service:ci . | |
| - name: Build search-service image | |
| run: docker build -f backend/search-service/Dockerfile -t search-service:ci . | |
| - name: Build cdc-service image | |
| run: docker build -f backend/cdc-service/Dockerfile -t cdc-service:ci . | |
| # Frontend uses its own directory as context | |
| - name: Build frontend image | |
| run: docker build -f frontend/Dockerfile -t frontend:ci frontend | |
| # --- 6. Frontend E2E smoke (docker compose) ----------------------------------- | |
| frontend-e2e-smoke: | |
| name: Frontend E2E Smoke | |
| runs-on: ubuntu-latest | |
| needs: [frontend] | |
| permissions: | |
| contents: read | |
| actions: read | |
| steps: | |
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| - name: Use Node 20 | |
| uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 | |
| with: | |
| node-version: 20 | |
| cache: npm | |
| cache-dependency-path: frontend/package-lock.json | |
| - name: Install frontend dependencies | |
| working-directory: frontend | |
| run: npm ci --ignore-scripts | |
| - name: Install Playwright Chromium deps | |
| working-directory: frontend | |
| run: npx playwright install --with-deps chromium | |
| - name: Prepare .env for Compose | |
| run: cp .env.example .env | |
| - name: Start stack for E2E smoke | |
| run: docker compose up -d --build | |
| - name: Wait for frontend endpoint | |
| shell: bash | |
| run: | | |
| for i in {1..60}; do | |
| if curl -fsS http://localhost:4200 > /dev/null; then | |
| echo "Frontend is up" | |
| exit 0 | |
| fi | |
| sleep 2 | |
| done | |
| echo "Frontend did not become ready in time" | |
| exit 1 | |
| - name: Run frontend E2E smoke | |
| working-directory: frontend | |
| run: npm run test:e2e-smoke | |
| - name: Dump compose logs on failure | |
| if: failure() | |
| run: docker compose logs --tail=200 | |
| - name: Stop stack | |
| if: always() | |
| run: docker compose down -v | |
| # --- 7. Telegram notification ------------------------------------------------- | |
| notify-telegram: | |
| name: Notify Telegram | |
| runs-on: ubuntu-latest | |
| needs: [backend, frontend, sonarqube, sonarqube-frontend, docker, frontend-e2e-smoke] | |
| if: ${{ always() }} | |
| env: | |
| TELEGRAM_TO: ${{ secrets.TELEGRAM_TO }} | |
| TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_TOKEN }} | |
| permissions: | |
| contents: read | |
| actions: read | |
| steps: | |
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| - name: Download backend test reports artifact | |
| if: always() | |
| continue-on-error: true | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 | |
| with: | |
| name: backend-test-reports | |
| path: test-reports/backend | |
| - name: Compose Telegram message | |
| id: compose | |
| shell: bash | |
| run: python3 .github/scripts/compose_telegram_message.py | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| BACKEND_RESULT: ${{ needs.backend.result }} | |
| FRONTEND_RESULT: ${{ needs.frontend.result }} | |
| SONAR_BACKEND_RESULT: ${{ needs.sonarqube.result }} | |
| SONAR_FRONTEND_RESULT: ${{ needs.sonarqube-frontend.result }} | |
| DOCKER_RESULT: ${{ needs.docker.result }} | |
| FRONTEND_E2E_SMOKE_RESULT: ${{ needs.frontend-e2e-smoke.result }} | |
| - name: Notify Telegram | |
| if: ${{ env.TELEGRAM_TO != '' && env.TELEGRAM_TOKEN != '' }} | |
| uses: appleboy/telegram-action@221e6b684967abe813051ee4a37dd61770a83ad3 # v1.0.1 | |
| with: | |
| to: ${{ secrets.TELEGRAM_TO }} | |
| token: ${{ secrets.TELEGRAM_TOKEN }} | |
| message: ${{ steps.compose.outputs.message }} | |
| format: html | |
| - name: Telegram skipped (missing secrets) | |
| if: ${{ env.TELEGRAM_TO == '' || env.TELEGRAM_TOKEN == '' }} | |
| run: echo "TELEGRAM_TO and/or TELEGRAM_TOKEN are not set. Skipping Telegram notification." | |