Skip to content

Fix: align frontend SonarQube project name with backend pattern #162

Fix: align frontend SonarQube project name with backend pattern

Fix: align frontend SonarQube project name with backend pattern #162

Workflow file for this run

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."