fix(frontend): resolve Sonar issues in app component and autocomplete service #113
Workflow file for this run
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. 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." | |