diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml new file mode 100644 index 0000000000..b437b0d023 --- /dev/null +++ b/.github/workflows/code-coverage.yml @@ -0,0 +1,140 @@ +name: 'Code Coverage' +on: + workflow_call: + inputs: + type: + description: Type of report (unit or integration) + type: string + resource_name: + description: Resource name of coverage report + type: string + +jobs: + coverage-unit: + runs-on: ubuntu-latest + name: Unit tests coverage + if: ${{ inputs.type == 'unit' }} + steps: + - uses: actions/checkout@v4 + + - name: Download Coverage Report + uses: actions/download-artifact@v4 + with: + name: ${{ inputs.resource_name }} + path: report + + - uses: jwalton/gh-find-current-pr@v1 + id: findPr + + - uses: ArtiomTr/jest-coverage-report-action@v2 + with: + prnumber: ${{ steps.findPr.outputs.number }} + coverage-file: report/coverage/report.json + base-coverage-file: report/coverage/report.json + github-token: ${{ secrets.GITHUB_TOKEN }} + skip-step: all + custom-title: Code Coverage - ${{ inputs.resource_name == 'report-be' && 'Backend' || 'Frontend' }} unit tests + + coverage-integration: + runs-on: ubuntu-latest + name: Integration tests coverage + if: ${{ inputs.type == 'integration' }} + steps: + - uses: actions/checkout@v4 + + - name: Download Coverage Report + uses: actions/download-artifact@v4 + with: + name: ${{ inputs.resource_name }} + + - name: Parse Coverage Summary + id: parse-coverage + run: | + # Extract coverage data from file. + # Example of processed row: + # Statements : 81.75% ( 16130/19730 ) + # field '$3' = 81.75%, field '$5' = 16130 + extract_coverage_data() { + local keyword=$1 + local field=$2 + awk "/$keyword/ {print $field}" integration-coverage.txt | tr -d '\n|%' + } + + # Determine status based on percentage + get_status() { + if [ "$(echo "$1 < 50" | bc)" -eq 1 ]; then + echo "🔴" + elif [ "$(echo "$1 < 80" | bc)" -eq 1 ]; then + echo "🟡" + else + echo "🟢" + fi + } + + # Extract coverage data from the summary + STATEMENTS_PERCENT=$(extract_coverage_data "Statements" '$3') + STATEMENTS_COVERED=$(extract_coverage_data "Statements" '$5') + STATEMENTS_STATUS=$(get_status $STATEMENTS_PERCENT) + + BRANCHES_PERCENT=$(extract_coverage_data "Branches" '$3') + BRANCHES_COVERED=$(extract_coverage_data "Branches" '$5') + BRANCHES_STATUS=$(get_status $BRANCHES_PERCENT) + + FUNCTIONS_PERCENT=$(extract_coverage_data "Functions" '$3') + FUNCTIONS_COVERED=$(extract_coverage_data "Functions" '$5') + FUNCTIONS_STATUS=$(get_status $FUNCTIONS_PERCENT) + + LINES_PERCENT=$(extract_coverage_data "Lines" '$3') + LINES_COVERED=$(extract_coverage_data "Lines" '$5') + LINES_STATUS=$(get_status $LINES_PERCENT) + + # Format as a Markdown table + echo "| Status | Category | Percentage | Covered / Total |" > coverage-table.md + echo "|-------------|-------------|-------------|-----------------|" >> coverage-table.md + echo "| $STATEMENTS_STATUS | Statements | ${STATEMENTS_PERCENT}% | ${STATEMENTS_COVERED} |" >> coverage-table.md + echo "| $BRANCHES_STATUS | Branches | ${BRANCHES_PERCENT}% | ${BRANCHES_COVERED} |" >> coverage-table.md + echo "| $FUNCTIONS_STATUS | Functions | ${FUNCTIONS_PERCENT}% | ${FUNCTIONS_COVERED} |" >> coverage-table.md + echo "| $LINES_STATUS | Lines | ${LINES_PERCENT}% | ${LINES_COVERED} |" >> coverage-table.md + + - uses: jwalton/gh-find-current-pr@v1 + id: findPr + + - name: Post or Update Coverage Summary Comment + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const table = fs.readFileSync('coverage-table.md', 'utf8'); + const commentBody = `### Code Coverage - Integration Tests\n\n${table}`; + + // Fetch existing comments on the pull request + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: process.env.RR_Number, + }); + + // Check if a comment with the same header already exists + const existingComment = comments.find(comment => + comment.body.startsWith('### Code Coverage - Integration Tests') + ); + + if (existingComment) { + // Update the existing comment + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existingComment.id, + body: commentBody, + }); + } else { + // Create a new comment + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: process.env.RR_Number, + body: commentBody, + }); + } + env: + RR_Number: ${{ steps.findPr.outputs.number }} diff --git a/.github/workflows/tests-integration.yml b/.github/workflows/tests-integration.yml index 45513874c2..8099e0eade 100644 --- a/.github/workflows/tests-integration.yml +++ b/.github/workflows/tests-integration.yml @@ -191,7 +191,14 @@ jobs: sudo mkdir -p /usr/src/app sudo cp -a ./redisinsight/api/. /usr/src/app/ sudo cp -R ./coverages /usr/src/app && sudo chmod 777 -R /usr/src/app - cd /usr/src/app && npx nyc report -t ./coverages -r text -r text-summary + cd /usr/src/app && npx nyc report -t ./coverages -r text -r text-summary > integration-coverage.txt + cp integration-coverage.txt $GITHUB_WORKSPACE/integration-coverage.txt + + - name: Upload integration-coverage as artifact + uses: actions/upload-artifact@v4 + with: + name: integration-coverage + path: integration-coverage.txt - name: Delete Artifact uses: actions/github-script@v7 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5d38bd21b3..7040d652c8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -94,12 +94,28 @@ jobs: uses: ./.github/workflows/tests-frontend.yml secrets: inherit + frontend-tests-coverage: + needs: frontend-tests + uses: ./.github/workflows/code-coverage.yml + secrets: inherit + with: + resource_name: report-fe + type: unit + backend-tests: needs: changes if: inputs.group_tests == 'all' || inputs.group_tests == 'without_e2e' || startsWith(github.ref_name, 'be/') || startsWith(github.ref_name, 'fe-be/') || startsWith(github.ref_name, 'feature/') || startsWith(github.ref_name, 'bugfix/') || startsWith(github.ref_name, 'ric/') uses: ./.github/workflows/tests-backend.yml secrets: inherit + backend-tests-coverage: + needs: backend-tests + uses: ./.github/workflows/code-coverage.yml + secrets: inherit + with: + resource_name: report-be + type: unit + integration-tests: needs: changes if: inputs.group_tests == 'all' || inputs.group_tests == 'without_e2e' || startsWith(github.ref_name, 'be/') || startsWith(github.ref_name, 'fe-be/') || startsWith(github.ref_name, 'feature/') || startsWith(github.ref_name, 'bugfix/') || startsWith(github.ref_name, 'ric/') @@ -110,6 +126,14 @@ jobs: redis_client: ${{ inputs.redis_client || '' }} debug: ${{ inputs.debug || false }} + integration-tests-coverage: + needs: integration-tests + uses: ./.github/workflows/code-coverage.yml + secrets: inherit + with: + resource_name: integration-coverage + type: integration + # # E2E Approve e2e-approve: runs-on: ubuntu-latest diff --git a/package.json b/package.json index f7310ef097..368c880143 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "test:api": "yarn --cwd redisinsight/api test", "test:api:integration": "yarn --cwd redisinsight/api test:api", "test:watch": "jest ./redisinsight/ui --watch -w 1", - "test:cov": "cross-env NODE_OPTIONS='' jest ./redisinsight/ui --silent --coverage --no-cache --forceExit -w 3", + "test:cov": "cross-env NODE_OPTIONS='' jest ./redisinsight/ui --testLocationInResults --json --outputFile=\"report/coverage/report.json\" --silent --coverage --no-cache --forceExit -w 3", "test:cov:unit": "jest ./redisinsight/ui --group=-component --coverage -w 1", "test:cov:component": "jest ./redisinsight/ui --group=component --coverage -w 1", "type-check:ui": "tsc --project redisinsight/ui --noEmit" diff --git a/redisinsight/api/package.json b/redisinsight/api/package.json index 167800402a..ea968bfacd 100644 --- a/redisinsight/api/package.json +++ b/redisinsight/api/package.json @@ -29,7 +29,7 @@ "start:prod": "cross-env NODE_ENV=production node dist/src/main", "test": "cross-env NODE_ENV=test ./node_modules/.bin/jest -w 1", "test:watch": "cross-env NODE_ENV=test jest --watch -w 1", - "test:cov": "cross-env NODE_ENV=test ./node_modules/.bin/jest --forceExit --coverage --runInBand", + "test:cov": "cross-env NODE_ENV=test ./node_modules/.bin/jest --testLocationInResults --json --outputFile=\"report/coverage/report.json\" --forceExit --coverage --runInBand", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand -w 1", "test:e2e": "jest --config ./test/jest-e2e.json -w 1", "typeorm": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js -d ./config/ormconfig.ts",