From dc78fdc16e311a6c2c466d656ddac883235c0d38 Mon Sep 17 00:00:00 2001 From: wojciech-deriv <141034155+wojciech-deriv@users.noreply.github.com> Date: Wed, 5 Jun 2024 10:06:45 +0100 Subject: [PATCH] [WALL-3660] feature/switch to total size (#15483) * feat: improved bundle analysis * feat: removed unnecessary table * feat: added a bit of tolerance for orange, so it will not trigger orange for too minuscule changes --- .github/actions/analyze/action.yml | 39 ++-- .github/actions/analyze/compareReports.js | 228 +++++++++++++--------- 2 files changed, 161 insertions(+), 106 deletions(-) diff --git a/.github/actions/analyze/action.yml b/.github/actions/analyze/action.yml index 2157065d8d07..67bb65435af1 100644 --- a/.github/actions/analyze/action.yml +++ b/.github/actions/analyze/action.yml @@ -73,17 +73,11 @@ runs: GITHUB_TOKEN: ${{ inputs.GITHUB_TOKEN }} shell: bash run: | - # Fetching the artifacts and storing response for debugging RESPONSE=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \ "https://api.github.com/repos/${{ github.repository }}/actions/artifacts?name=analyse") - # uncomment for debugging purposes - # echo "API Response: $RESPONSE" - - # Extracting the artifact URL using jq ARTIFACT_URL=$(echo $RESPONSE | jq -r '.artifacts[0].archive_download_url') - # Check if the artifact URL is empty if [[ -z "$ARTIFACT_URL" ]]; then echo "Error: No artifact URL found for the master branch with prefix 'analyse'." exit 1 @@ -91,7 +85,6 @@ runs: echo "Artifact URL: $ARTIFACT_URL" fi - # Output artifact URL for other steps echo "artifact_url=$ARTIFACT_URL" >> $GITHUB_OUTPUT echo "artifact_url=$ARTIFACT_URL" @@ -138,36 +131,50 @@ runs: if: steps.get_artifact_url.outputs.artifact_url != 'null' shell: bash run: | - DIFF_OUTPUT=$(node .github/actions/analyze/compareReports.js) - echo "diff_output<> $GITHUB_OUTPUT - echo "$DIFF_OUTPUT" >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT - echo $DIFF_OUTPUT + DIFF_OUTPUT_HTML=$(node .github/actions/analyze/compareReports.js --format=html) + ABOVE_THRESHOLD=$(node .github/actions/analyze/compareReports.js --format=boolean) || { echo "Threshold check failed"; exit 1; } + + # Output results to GITHUB_OUTPUT + echo "diff_output_html=$DIFF_OUTPUT_HTML" >> $GITHUB_OUTPUT + echo "above_threshold=$ABOVE_THRESHOLD" >> $GITHUB_OUTPUT - name: Comment on PR with Diff Output if: steps.get_artifact_url.outputs.artifact_url != 'null' && inputs.ISSUE_NUMBER uses: actions/github-script@v5 env: - DIFF_OUTPUT: ${{ steps.diff.outputs.diff_output }} + DIFF_OUTPUT_HTML: ${{ steps.diff.outputs.diff_output_html }} ISSUE_NUMBER: ${{ inputs.ISSUE_NUMBER }} with: script: | - const diffOutput = process.env.DIFF_OUTPUT; + const diffOutputHtml = process.env.DIFF_OUTPUT_HTML; // Removed Buffer.from and base64 decoding const issueNumber = process.env.ISSUE_NUMBER; github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: issueNumber, - body: `${diffOutput}` + body: `${diffOutputHtml}` }); continue-on-error: true + - name: Print out differences on console + id: print_diff + if: steps.get_artifact_url.outputs.artifact_url != 'null' + shell: bash + run: | + node .github/actions/analyze/compareReports.js --format=console + + - name: Validate size changes + if: ${{ steps.diff.outputs.above_threshold == 'true' }} + uses: actions/github-script@v5 + with: + script: | + core.setFailed('Size changes exceed the defined threshold. Check above logs for details.'); + - name: Zip all report.json files shell: bash run: | zip -r analyse.zip packages/*/report.json - # Step for the master branch - name: Upload analyse.zip for Master Branch if: github.ref == 'refs/heads/master' uses: actions/upload-artifact@v4 diff --git a/.github/actions/analyze/compareReports.js b/.github/actions/analyze/compareReports.js index e6a69d5c112d..f84087521b04 100644 --- a/.github/actions/analyze/compareReports.js +++ b/.github/actions/analyze/compareReports.js @@ -1,122 +1,170 @@ const fs = require('fs'); const path = require('path'); -function roundUpToDecimals(num, decimals) { - const factor = Math.pow(10, decimals); - return Math.ceil(num * factor) / factor; -} - -function readJsonFile(filePath) { - if (fs.existsSync(filePath)) { - const data = fs.readFileSync(filePath, 'utf-8'); - return JSON.parse(data); +// read and use parameters from command line +const args = process.argv.slice(2); // Skip the first two elements +let format = args.find(arg => arg.startsWith('--format='))?.split('=')[1] || 'html'; +let orangeThreshold = +(args.find(arg => arg.startsWith('--orangeThreshold='))?.split('=')[1] || 0.5); +let redThreshold = +(args.find(arg => arg.startsWith('--redThreshold='))?.split('=')[1] || 5); + +// main function execution +main(); + +function main() { + // format: [package]: { oldSize, newSize, diff, percentage } + const sizes = analyse(); + + // format to different output based on the format parameter + // nice table in html if its for comment, nice table in console if its for console, or just true/false if its just to check validity + if (format === 'html') { + let formattedOutput = formatToTable(sizes); + console.log(formattedOutput); + } else if (format === 'console') { + let formattedOutput = formatToConsole(sizes); + console.table(formattedOutput, ['oldSize', 'newSize', 'diff', 'percentage', 'alert']); + } else if (format === 'boolean') { + const aboveRedThreshold = Object.values(sizes).some(pkg => pkg.percentage > redThreshold); + if (aboveRedThreshold) { + console.log('true'); + } else { + console.log('false'); + } } - return null; } -function calculateDiff(oldSize, newSize) { - return newSize - oldSize; -} +function analyse() { + const packagesDir = './packages'; + const oldPackagesDir = './old/packages'; -function calculatePercentage(oldSize, newSize) { - if (oldSize === 0) { - return newSize === 0 ? 0 : 100; - } - return ((newSize - oldSize) / oldSize) * 100; -} + const packages = [...new Set([...fs.readdirSync(packagesDir), ...fs.readdirSync(oldPackagesDir)])]; -function formatSize(size, roundUp) { - if (size === null) { - return '-'; - } + const result = {}; - const formattedSize = roundUp ? roundUpToDecimals(size / 1024, 2) + 'kb' : (size / 1024).toFixed(2) + 'kb'; - return formattedSize; -} + for (const pkg of packages) { + const oldReport = readJsonFile(path.join(oldPackagesDir, pkg, 'report.json')); + const newReport = readJsonFile(path.join(packagesDir, pkg, 'report.json')); + + if (!newReport) { + continue; + } -const packagesDir = './packages'; -const oldPackagesDir = './old/packages'; -const packages = [...new Set([...fs.readdirSync(packagesDir), ...fs.readdirSync(oldPackagesDir)])]; + const oldSize = oldReport ? oldReport.reduce((acc, item) => acc + item.gzipSize, 0) : null; + const newSize = newReport ? newReport.reduce((acc, item) => acc + item.gzipSize, 0) : null; -let tableRows = ''; + let diff = oldSize && newSize ? newSize - oldSize : oldSize || newSize; + let percentage = oldSize && newSize ? calculatePercentage(oldSize, newSize) : null; -for (const pkg of packages) { - const oldReport = readJsonFile(path.join(oldPackagesDir, pkg, 'report.json')); - const newReport = readJsonFile(path.join(packagesDir, pkg, 'report.json')); + result[pkg] = { + oldSize, + newSize, + diff, + percentage, + }; + } - const oldSize = oldReport ? oldReport[0].gzipSize : null; - const newSize = newReport ? newReport[0].gzipSize : null; - let diff = oldSize && newSize ? calculateDiff(oldSize, newSize) : null; + return result; +} - if (oldSize === null) { - diff = newSize; +function formatToTable(sizes) { + const GREEN_SIGN = '🟢'; + const YELLOW_SIGN = '🟡'; + const RED_SIGN = '🔴'; + + let tableRows = ''; + for (const [pkg, { oldSize, newSize, diff, percentage }] of Object.entries(sizes)) { + const formattedPercentage = formatPercentageWithSign(percentage); + const lightSign = + percentage > redThreshold ? RED_SIGN : percentage > orangeThreshold ? YELLOW_SIGN : GREEN_SIGN; + + tableRows += ` + + ${pkg} + ${formatBytes(oldSize)} + ${formatBytes(newSize)} + ${formatBytes(diff, true)} + ${formattedPercentage} ${lightSign} + + `.trim(); } - if (newSize === null) { - diff = oldSize; + return ` + + + + + + + + + + ${tableRows} + +
packageoldnewdiffpct change
` + .replace(/[\n\t]/g, '') + .trim(); +} + +function formatToConsole(sizes) { + Object.keys(sizes).forEach(key => { + const pkg = sizes[key]; + pkg.oldSize = formatBytes(pkg.oldSize); + pkg.newSize = formatBytes(pkg.newSize); + pkg.diff = formatBytes(pkg.diff, true); + pkg.alert = pkg.percentage > redThreshold ? 'FAIL' : pkg.percentage > orangeThreshold ? 'WARN' : 'OK'; + pkg.percentage = formatPercentageWithSign(pkg.percentage); + }); + return sizes; +} + +function readJsonFile(filePath) { + if (fs.existsSync(filePath)) { + const data = fs.readFileSync(filePath, 'utf-8'); + return JSON.parse(data); } + return null; +} - let diffText = '-'; +function calculatePercentage(oldSize, newSize) { + return ((newSize - oldSize) / oldSize) * 100; +} - if (diff !== 0) { - diffText = diff < 0 ? '-' : '+' + formatSize(diff, true); - } else { - diffText = '+0kb'; +function formatBytes(bytes, sign = false) { + if (bytes === null || isNaN(bytes)) { + return 'n/a'; } - let percentage = oldSize && newSize ? calculatePercentage(oldSize, newSize) : null; + let formattedValue = ''; - if (oldSize === null) { - percentage = 100; + if (bytes < 1024) { + formattedValue = bytes + ' B'; // Bytes + } else if (bytes < 1048576) { + formattedValue = Math.round(bytes / 1024) + ' KB'; // Kilobytes + } else { + formattedValue = (bytes / 1048576).toFixed(1) + ' MB'; // Megabytes } - if (newSize === null) { - percentage = -100; + if (sign) { + if (bytes === 0) { + return '0 B'; + } + formattedValue = bytes >= 0 ? '+' + formattedValue : '-' + formattedValue; } - let percentageText = '-'; - let percentageEmoji; + return formattedValue; +} - if (percentage === 0) { - percentageEmoji = ''; - } else if (percentage < 0) { - percentageEmoji = '🟢'; // green for decrease - } else if (percentage >= 0 && percentage <= 5) { - percentageEmoji = '🟡'; // yellow for small increase - } else { - percentageEmoji = '🔴'; // red for larger increase +function formatPercentageWithSign(percentage) { + if (percentage === null || isNaN(percentage)) { + return 'n/a'; } - if (percentage !== 0) { - percentageText = percentage.toFixed(2) + '%'; - } else { - percentageText = '0%'; + const absPercentage = Math.abs(percentage); + const decimalPoints = absPercentage < 10 ? 1 : 2; + let formattedValue = percentage.toFixed(decimalPoints) + '%'; + + if (percentage === 0) { + return '0%'; } - tableRows += ` - - ${pkg} - ${formatSize(oldSize)} - ${formatSize(newSize)} - ${diffText} - ${percentageText} ${percentageEmoji} - - `.trim(); + return percentage >= 0 ? '+' + formattedValue : '−' + formattedValue; } - -console.log( - ` - - - - - - - - - - ${tableRows} - -
packageoldnewdiffpercentage
-`.trim() -);