Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions .github/actions/flaky-test-report/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: Flaky Test Report
description: 'Generates a report of flaky tests from a specified GitHub Actions workflow and sends it to a Slack channel.'

inputs:
repository:
description: 'Repository name (e.g. metamask-extension)'
required: true
workflow-id:
description: 'Workflow ID to analyze (e.g. main.yml)'
required: true
github-token:
description: 'GitHub token with repo and actions:read access'
required: true
slack-webhook-flaky-tests:
description: 'Slack webhook URL for flaky test reports'
required: true

runs:
using: composite
steps:
- name: Checkout github-tools repository
uses: actions/checkout@v4
with:
repository: MetaMask/github-tools
path: github-tools

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version-file: ./github-tools/.nvmrc
cache-dependency-path: ./github-tools/yarn.lock
cache: yarn

- name: Enable Corepack
working-directory: ./github-tools
shell: bash
run: corepack enable

- name: Install dependencies
working-directory: ./github-tools
shell: bash
run: yarn --immutable

- name: Run flaky test report script
env:
REPOSITORY: ${{ inputs.repository }}
WORKFLOW_ID: ${{ inputs.workflow-id }}
GITHUB_TOKEN: ${{ inputs.github-token }}
SLACK_WEBHOOK_FLAKY_TESTS: ${{ inputs.slack-webhook-flaky-tests }}
working-directory: ./github-tools
shell: bash
run: node .github/scripts/create-flaky-test-report.mjs
50 changes: 50 additions & 0 deletions .github/actions/log-merge-group-failure/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Log Merge Group Failure

inputs:
google-application-credentials:
description: 'Path to Google application credentials JSON file.'
required: true
google-service-account:
description: 'Base64 encoded Google service account JSON.'
required: true
spreadsheet-id:
description: 'Google Spreadsheet ID.'
required: true
sheet-name:
description: 'Sheet tab name.'
required: true

runs:
using: composite
steps:
- name: Download oauth2l
shell: bash
run: |
curl --silent https://storage.googleapis.com/oauth2l/1.3.2/linux_amd64.tgz | tar xz
echo "$PWD/linux_amd64" >> "$GITHUB_PATH"

- name: Create service_account.json
env:
GOOGLE_APPLICATION_CREDENTIALS: ${{ inputs.google-application-credentials }}
GOOGLE_SERVICE_ACCOUNT: ${{ inputs.google-service-account }}
shell: bash
run: |
echo "$GOOGLE_SERVICE_ACCOUNT" > "$GOOGLE_APPLICATION_CREDENTIALS"

- name: Write data to google sheets
env:
GOOGLE_APPLICATION_CREDENTIALS: ${{ inputs.google-application-credentials }}
SPREADSHEET_ID: ${{ inputs.spreadsheet-id }}
SHEET_NAME: ${{ inputs.sheet-name }}
shell: bash
run: |
current_date=$(date +%Y-%m-%d)
token=$(oauth2l fetch --scope https://www.googleapis.com/auth/spreadsheets)
spreadsheet_data=$(curl --silent --header "Authorization: Bearer $token" https://sheets.googleapis.com/v4/spreadsheets/"$SPREADSHEET_ID"/values/"$SHEET_NAME"!A:B)
current_date_index=$(echo "$spreadsheet_data" | jq --arg current_date "$current_date" '(.values | map(.[0])) | (index($current_date) | if . == null then null else . + 1 end)')
current_number_of_prs=$(echo "$spreadsheet_data" | jq --arg current_date "$current_date" '(.values[] | select(.[0] == $current_date) | .[1] | tonumber) // null')
if [ "$current_date_index" == "null" ]; then
curl --silent --header "Authorization: Bearer $token" --header "Content-Type: application/json" --request POST --data "{\"values\":[[\"$current_date\", 1]]}" https://sheets.googleapis.com/v4/spreadsheets/"$SPREADSHEET_ID"/values/"$SHEET_NAME"!A:A:append?valueInputOption=USER_ENTERED
else
curl --silent --header "Authorization: Bearer $token" --header "Content-Type: application/json" --request PUT --data "{\"values\":[[\"$current_date\", $(("$current_number_of_prs" + 1))]]}" https://sheets.googleapis.com/v4/spreadsheets/"$SPREADSHEET_ID"/values/"$SHEET_NAME"!A"$current_date_index":B"$current_date_index"?valueInputOption=USER_ENTERED
fi
203 changes: 203 additions & 0 deletions .github/actions/pr-line-check/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
name: Check PR Lines Changed
description: 'Checks the number of lines changed in a PR and manages size labels accordingly.'

inputs:
max-lines:
description: 'Maximum allowed total lines changed'
required: false
default: '1000'
base-ref:
description: 'Default base branch to compare against (if not running on a PR)'
required: false
default: 'main'
ignore-patterns:
description: 'Regex pattern for files to ignore when calculating changes'
required: false
default: '(\.lock$)'
xs-max-size:
description: 'Maximum lines for XS size'
required: false
default: '10'
s-max-size:
description: 'Maximum lines for S size'
required: false
default: '100'
m-max-size:
description: 'Maximum lines for M size'
required: false
default: '500'
l-max-size:
description: 'Maximum lines for L size'
required: false
default: '1000'

runs:
using: composite
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Calculate changed lines
id: line-count
env:
BASE_BRANCH: ${{ github.event.pull_request.base.ref || inputs.base-ref }}
IGNORE_PATTERNS: ${{ inputs.ignore-patterns }}
shell: bash
run: |
set -e

echo "Using base branch: $BASE_BRANCH"

# Instead of a full fetch, perform incremental fetches at increasing depth
# until the merge-base between origin/<BASE_BRANCH> and HEAD is present.
fetch_with_depth() {
local depth=$1
echo "Attempting to fetch with depth $depth..."
git fetch --depth="$depth" origin "$BASE_BRANCH"
}

depths=(1 10 100)
merge_base_found=false

for d in "${depths[@]}"; do
fetch_with_depth "$d"
if git merge-base "origin/$BASE_BRANCH" HEAD > /dev/null 2>&1; then
echo "Merge base found with depth $d."
merge_base_found=true
break
else
echo "Merge base not found with depth $d, increasing depth..."
fi
done

# If we haven't found the merge base with shallow fetches, unshallow the repo.
if [ "$merge_base_found" = false ]; then
echo "Could not find merge base with shallow fetches, fetching full history..."
git fetch --unshallow origin "$BASE_BRANCH" || git fetch origin "$BASE_BRANCH"
fi

# Calculate additions and deletions across all changes between the base and HEAD,
# filtering out files matching the ignore pattern.
additions=$(git diff "origin/$BASE_BRANCH"...HEAD --numstat | grep -Ev "$IGNORE_PATTERNS" | awk '{add += $1} END {print add+0}')
deletions=$(git diff "origin/$BASE_BRANCH"...HEAD --numstat | grep -Ev "$IGNORE_PATTERNS" | awk '{del += $2} END {print del+0}')
total=$((additions + deletions))

echo "Additions: $additions, Deletions: $deletions, Total: $total"
{
echo "lines-changed=$total"
echo "additions=$additions"
echo "deletions=$deletions"
} >> "$GITHUB_OUTPUT"

- name: Check line count limit
uses: actions/github-script@v7
env:
LINES_CHANGED: ${{ steps.line-count.outputs.lines-changed }}
ADDITIONS: ${{ steps.line-count.outputs.additions }}
DELETIONS: ${{ steps.line-count.outputs.deletions }}
MAX_LINES: ${{ inputs.max-lines }}
XS_MAX_SIZE: ${{ inputs.xs-max-size }}
S_MAX_SIZE: ${{ inputs.s-max-size }}
M_MAX_SIZE: ${{ inputs.m-max-size }}
L_MAX_SIZE: ${{ inputs.l-max-size }}
with:
script: |
const {
LINES_CHANGED,
ADDITIONS,
DELETIONS,
MAX_LINES,
XS_MAX_SIZE,
S_MAX_SIZE,
M_MAX_SIZE,
L_MAX_SIZE,
} = process.env;

const total = parseInt(LINES_CHANGED, 10) || 0;
const additions = parseInt(ADDITIONS, 10) || 0;
const deletions = parseInt(DELETIONS, 10) || 0;

// Thresholds from inputs with fallback to defaults
const maxLines = parseInt(MAX_LINES, 10) || 1000;
const xsMaxSize = parseInt(XS_MAX_SIZE, 10) || 10;
const sMaxSize = parseInt(S_MAX_SIZE, 10) || 100;
const mMaxSize = parseInt(M_MAX_SIZE, 10) || 500;
const lMaxSize = parseInt(L_MAX_SIZE, 10) || 1000;

// Print summary
console.log('Summary:');
console.log(` - Additions: ${additions}`);
console.log(` - Deletions: ${deletions}`);
console.log(` - Total: ${total}`);
console.log(` - Limit: ${maxLines}`);

// Determine size label based on configured criteria
let sizeLabel = '';
if (total <= xsMaxSize) {
sizeLabel = 'size-XS';
} else if (total <= sMaxSize) {
sizeLabel = 'size-S';
} else if (total <= mMaxSize) {
sizeLabel = 'size-M';
} else if (total <= lMaxSize) {
sizeLabel = 'size-L';
} else {
sizeLabel = 'size-XL';
}

console.log(` - Size category: ${sizeLabel}`);

// Manage PR labels
const owner = context.repo.owner;
const repo = context.repo.repo;
const issue_number = context.payload.pull_request.number;

try {
const existingSizeLabels = ['size-XS', 'size-S', 'size-M', 'size-L', 'size-XL'];

// Get current labels
const currentLabels = await github.rest.issues.listLabelsOnIssue({
owner,
repo,
issue_number
});

const currentLabelNames = currentLabels.data.map(l => l.name);

// Build new label set: keep non-size labels and add the new size label
const newLabels = currentLabelNames
.filter(name => !existingSizeLabels.includes(name)) // Remove all size labels
.concat(sizeLabel); // Add the correct size label

// Check if labels need updating
const currentSizeLabel = currentLabelNames.find(name => existingSizeLabels.includes(name));
if (currentSizeLabel === sizeLabel && currentLabelNames.length === newLabels.length) {
console.log(`✅ Correct label '${sizeLabel}' already present, no changes needed`);
} else {
// Update all labels in a single API call
await github.rest.issues.setLabels({
owner,
repo,
issue_number,
labels: newLabels
});

if (currentSizeLabel && currentSizeLabel !== sizeLabel) {
console.log(` - Replaced '${currentSizeLabel}' with '${sizeLabel}'`);
} else if (!currentSizeLabel) {
console.log(`✅ Added '${sizeLabel}' label to PR #${issue_number}`);
} else {
console.log(`✅ Updated labels for PR #${issue_number}`);
}
}
} catch (error) {
console.log(`⚠️ Could not manage labels: ${error.message}`);
}

// Check if exceeds limit
if (total > maxLines) {
console.log(`❌ Error: Total changed lines (${total}) exceed the limit of ${maxLines}.`);
process.exit(1);
} else {
console.log(`✅ Success: Total changed lines (${total}) are within the limit of ${maxLines}.`);
}
54 changes: 0 additions & 54 deletions .github/workflows/flaky-test-report.yml

This file was deleted.

Loading
Loading