Skip to content

fix(frontend): resolve Sonar issues in app component and autocomplete service #113

fix(frontend): resolve Sonar issues in app component and autocomplete service

fix(frontend): resolve Sonar issues in app component and autocomplete service #113

Workflow file for this run

name: CI
on:
push:
branches: [ main ]
pull_request:
permissions:
contents: read
jobs:
# --- 1. Shared library -------------------------------------------------------
common:
name: Build common
runs-on: ubuntu-latest
permissions:
contents: read
actions: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up JDK 25
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: temurin
java-version: 25
cache: maven
- name: Install common into local Maven cache
run: mvn -B -q -f common/pom.xml -DskipTests install
# Share the populated Maven cache with downstream jobs via upload-artifact
# (cheap: only the lt.satsyuk:common jar + metadata)
- name: Cache common jar for downstream jobs
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: common-m2
path: ~/.m2/repository/lt/satsyuk/common
retention-days: 1
# --- 2. Backend unit tests ---------------------------------------------------
backend-unit:
name: Unit - ${{ matrix.service }}
runs-on: ubuntu-latest
needs: common
permissions:
contents: read
actions: write
checks: write
strategy:
matrix:
service: [autocomplete-service, search-service, cdc-service]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up JDK 25
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: temurin
java-version: 25
cache: maven
- name: Restore common artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: common-m2
path: ~/.m2/repository/lt/satsyuk/common
- name: Run unit tests
run: mvn -B -ntp -f ${{ matrix.service }}/pom.xml test jacoco:report
- name: Upload unit coverage report
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: unit-coverage-${{ matrix.service }}
path: ${{ matrix.service }}/target/site/jacoco/jacoco.xml
if-no-files-found: error
retention-days: 7
- name: Upload unit test reports
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: unit-test-reports-${{ matrix.service }}
path: ${{ matrix.service }}/target/surefire-reports/TEST-*.xml
if-no-files-found: warn
retention-days: 7
- name: Unit test summary
if: always()
uses: dorny/test-reporter@a43b3a5f7366b97d083190328d2c652e1a8b6aa2 # v3.0.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
name: Unit Tests (${{ matrix.service }})
path: ${{ matrix.service }}/target/surefire-reports/TEST-*.xml
reporter: java-junit
fail-on-error: false
fail-on-empty: false
use-actions-summary: true
# --- 3. Backend integration tests --------------------------------------------
backend-integration:
name: Integration - ${{ matrix.service }}
runs-on: ubuntu-latest
needs: common
permissions:
contents: read
actions: write
checks: write
strategy:
matrix:
service: [autocomplete-service, search-service, cdc-service]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up JDK 25
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: temurin
java-version: 25
cache: maven
- name: Restore common artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: common-m2
path: ~/.m2/repository/lt/satsyuk/common
- name: Run integration tests
run: mvn -B -ntp -f ${{ matrix.service }}/pom.xml test-compile failsafe:integration-test failsafe:verify jacoco:report
- name: Upload integration coverage report
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: integration-coverage-${{ matrix.service }}
path: ${{ matrix.service }}/target/site/jacoco/jacoco.xml
if-no-files-found: error
retention-days: 7
- name: Upload integration test reports
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: integration-test-reports-${{ matrix.service }}
path: ${{ matrix.service }}/target/failsafe-reports/TEST-*.xml
if-no-files-found: warn
retention-days: 7
- name: Integration test summary
if: always()
uses: dorny/test-reporter@a43b3a5f7366b97d083190328d2c652e1a8b6aa2 # v3.0.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
name: Integration Tests (${{ matrix.service }})
path: ${{ matrix.service }}/target/failsafe-reports/TEST-*.xml
reporter: java-junit
fail-on-error: false
fail-on-empty: false
use-actions-summary: true
# --- 4. Frontend -------------------------------------------------------------
frontend:
name: Frontend (Angular)
runs-on: ubuntu-latest
permissions:
contents: read
actions: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Use Node 20
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.0.0
with:
node-version: 20
cache: npm
cache-dependency-path: frontend/package-lock.json
- name: Install dependencies
working-directory: frontend
run: npm ci
- 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
# --- 5. SonarQube ------------------------------------------------------------
sonarqube:
name: SonarQube - ${{ matrix.service }}
runs-on: ubuntu-latest
needs: [backend-unit, backend-integration]
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 }}
strategy:
matrix:
service: [autocomplete-service, search-service, cdc-service]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- name: Set up JDK 25
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: temurin
java-version: 25
cache: maven
- name: Restore common artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: common-m2
path: ~/.m2/repository/lt/satsyuk/common
- name: SonarQube skipped (missing token)
if: ${{ env.SONAR_TOKEN == '' }}
run: echo "SONAR_TOKEN is not set. Skipping SonarQube analysis."
- name: SonarQube skipped (missing organization on SonarCloud)
if: ${{ env.SONAR_TOKEN != '' && (vars.SONAR_HOST_URL == '' || vars.SONAR_HOST_URL == 'https://sonarcloud.io') && vars.SONAR_ORGANIZATION == '' }}
run: echo "SONAR_ORGANIZATION is required for SonarCloud. Skipping backend SonarQube analysis."
- name: Cache SonarQube packages
if: ${{ env.SONAR_TOKEN != '' && ((vars.SONAR_HOST_URL != '' && vars.SONAR_HOST_URL != 'https://sonarcloud.io') || vars.SONAR_ORGANIZATION != '') }}
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.2.3
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar-${{ matrix.service }}
restore-keys: ${{ runner.os }}-sonar-
- name: Download unit coverage report
if: ${{ env.SONAR_TOKEN != '' && ((vars.SONAR_HOST_URL != '' && vars.SONAR_HOST_URL != 'https://sonarcloud.io') || vars.SONAR_ORGANIZATION != '') }}
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: unit-coverage-${{ matrix.service }}
path: coverage/${{ matrix.service }}/unit
- name: Download integration coverage report
if: ${{ env.SONAR_TOKEN != '' && ((vars.SONAR_HOST_URL != '' && vars.SONAR_HOST_URL != 'https://sonarcloud.io') || vars.SONAR_ORGANIZATION != '') }}
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: integration-coverage-${{ matrix.service }}
path: coverage/${{ matrix.service }}/integration
- name: Reorganize coverage reports for SonarQube
if: ${{ env.SONAR_TOKEN != '' && ((vars.SONAR_HOST_URL != '' && vars.SONAR_HOST_URL != 'https://sonarcloud.io') || vars.SONAR_ORGANIZATION != '') }}
shell: bash
run: |
SERVICE="${{ matrix.service }}"
mkdir -p coverage/$SERVICE/unit coverage/$SERVICE/integration
# Find unit coverage XML and ensure it's at the expected location
unit_xml=$(find coverage/$SERVICE/unit -name "jacoco.xml" 2>/dev/null | head -1)
if [ -f "$unit_xml" ]; then
# Only copy if source and target are different
if [ "$unit_xml" != "coverage/$SERVICE/unit/jacoco.xml" ]; then
cp "$unit_xml" coverage/$SERVICE/unit/jacoco.xml
echo "Copied unit coverage: $unit_xml -> coverage/$SERVICE/unit/jacoco.xml"
else
echo "Unit coverage already at expected location: $unit_xml"
fi
fi
# Find integration coverage XML and ensure it's at the expected location
integration_xml=$(find coverage/$SERVICE/integration -name "jacoco.xml" 2>/dev/null | head -1)
if [ -f "$integration_xml" ]; then
# Only copy if source and target are different
if [ "$integration_xml" != "coverage/$SERVICE/integration/jacoco.xml" ]; then
cp "$integration_xml" coverage/$SERVICE/integration/jacoco.xml
echo "Copied integration coverage: $integration_xml -> coverage/$SERVICE/integration/jacoco.xml"
else
echo "Integration coverage already at expected location: $integration_xml"
fi
fi
# List files for verification
echo "Final coverage structure:"
find coverage/$SERVICE -name "*.xml" | sort || echo "No XML files found"
# Fail fast if coverage reports are still missing after reorganization
[ -f "coverage/$SERVICE/unit/jacoco.xml" ] || { echo "Missing unit coverage report for $SERVICE"; exit 1; }
[ -f "coverage/$SERVICE/integration/jacoco.xml" ] || { echo "Missing integration coverage report for $SERVICE"; exit 1; }
- name: SonarQube analysis
if: ${{ env.SONAR_TOKEN != '' && ((vars.SONAR_HOST_URL != '' && vars.SONAR_HOST_URL != 'https://sonarcloud.io') || vars.SONAR_ORGANIZATION != '') }}
env:
SONAR_HOST_URL: ${{ vars.SONAR_HOST_URL }}
SONAR_ORGANIZATION: ${{ vars.SONAR_ORGANIZATION }}
REPO_NAME: ${{ github.event.repository.name }}
SERVICE: ${{ matrix.service }}
shell: bash
run: |
host_url="${SONAR_HOST_URL:-https://sonarcloud.io}"
project_key="${REPO_NAME}-${SERVICE}"
unit_report="${GITHUB_WORKSPACE}/coverage/${SERVICE}/unit/jacoco.xml"
integration_report="${GITHUB_WORKSPACE}/coverage/${SERVICE}/integration/jacoco.xml"
cmd=(
mvn -B -ntp
-f "${SERVICE}/pom.xml"
-DskipTests
test-compile
sonar:sonar
"-Dsonar.host.url=${host_url}"
"-Dsonar.projectKey=${project_key}"
"-Dsonar.coverage.jacoco.xmlReportPaths=${unit_report},${integration_report}"
)
if [ -n "${SONAR_ORGANIZATION}" ]; then
cmd+=("-Dsonar.organization=${SONAR_ORGANIZATION}")
fi
"${cmd[@]}"
# --- 6. 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 }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- name: Use Node 20
if: ${{ env.SONAR_TOKEN != '' }}
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.0.0
with:
node-version: 20
cache: npm
cache-dependency-path: frontend/package-lock.json
- name: Install frontend dependencies
if: ${{ env.SONAR_TOKEN != '' }}
working-directory: frontend
run: npm ci
- name: SonarQube skipped (missing token)
if: ${{ env.SONAR_TOKEN == '' }}
run: echo "SONAR_TOKEN is not set. Skipping SonarQube analysis."
- name: Download frontend coverage report
if: ${{ env.SONAR_TOKEN != '' && ((vars.SONAR_HOST_URL != '' && vars.SONAR_HOST_URL != 'https://sonarcloud.io') || vars.SONAR_ORGANIZATION != '') }}
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: frontend-coverage
path: frontend/coverage
- name: Ensure lcov.info is at expected path
if: ${{ env.SONAR_TOKEN != '' && ((vars.SONAR_HOST_URL != '' && vars.SONAR_HOST_URL != 'https://sonarcloud.io') || vars.SONAR_ORGANIZATION != '') }}
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
[ -f "$expected" ] || { echo "lcov.info not found after reorganization"; exit 1; }
echo "Coverage report verified at: $expected"
- name: Configure SonarQube organization
if: ${{ env.SONAR_TOKEN != '' && vars.SONAR_ORGANIZATION != '' }}
run: echo "sonar.organization=${{ vars.SONAR_ORGANIZATION }}" >> frontend/sonar-project.properties
- name: SonarQube frontend skipped (missing organization on SonarCloud)
if: ${{ env.SONAR_TOKEN != '' && (vars.SONAR_HOST_URL == '' || vars.SONAR_HOST_URL == 'https://sonarcloud.io') && vars.SONAR_ORGANIZATION == '' }}
run: echo "SONAR_ORGANIZATION is required for SonarCloud. Skipping frontend SonarQube analysis."
- name: SonarQube analysis (frontend)
if: ${{ env.SONAR_TOKEN != '' && ((vars.SONAR_HOST_URL != '' && vars.SONAR_HOST_URL != 'https://sonarcloud.io') || vars.SONAR_ORGANIZATION != '') }}
uses: SonarSource/sonarqube-scan-action@59db25f34e16620e48ab4bb9e4a5dce155cb5432 # v8.0.0
env:
SONAR_HOST_URL: ${{ vars.SONAR_HOST_URL || 'https://sonarcloud.io' }}
with:
projectBaseDir: frontend
args: >
-Dsonar.projectKey=${{ github.event.repository.name }}-frontend
# --- 7. Docker build (no push on PRs) ----------------------------------------
docker:
name: Docker Build
runs-on: ubuntu-latest
needs: [backend-unit, backend-integration, frontend]
permissions:
contents: read
actions: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Restore common artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: common-m2
path: ~/.m2/repository/lt/satsyuk/common
# Java services need the repo root as build context (they COPY common/)
- name: Build autocomplete-service image
run: docker build -f autocomplete-service/Dockerfile -t autocomplete-service:ci .
- name: Build search-service image
run: docker build -f search-service/Dockerfile -t search-service:ci .
- name: Build cdc-service image
run: docker build -f 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
# --- 8. Telegram notification -------------------------------------------------
notify-telegram:
name: Notify Telegram
runs-on: ubuntu-latest
needs: [backend-unit, backend-integration, frontend, sonarqube, sonarqube-frontend, docker]
if: ${{ always() }}
env:
TELEGRAM_TO: ${{ secrets.TELEGRAM_TO }}
TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_TOKEN }}
permissions:
contents: read
actions: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Download unit test reports artifacts
if: always()
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
pattern: unit-test-reports-*
path: test-reports/unit
if-no-artifact-found: ignore
- name: Download integration test reports artifacts
if: always()
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
pattern: integration-test-reports-*
path: test-reports/integration
if-no-artifact-found: ignore
- name: Compose Telegram message
id: compose
shell: bash
run: python3 .github/scripts/compose_telegram_message.py
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BACKEND_UNIT_RESULT: ${{ needs.backend-unit.result }}
BACKEND_INT_RESULT: ${{ needs.backend-integration.result }}
FRONTEND_RESULT: ${{ needs.frontend.result }}
SONAR_BACKEND_RESULT: ${{ needs.sonarqube.result }}
SONAR_FRONTEND_RESULT: ${{ needs.sonarqube-frontend.result }}
DOCKER_RESULT: ${{ needs.docker.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."