mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-24 13:54:57 +00:00
Fix triage script to dynamically find and close duplicates using GitHub API
Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com>
This commit is contained in:
@@ -43,20 +43,32 @@ Now it only runs when the `deploy-production` job actually fails.
|
||||
|
||||
A script was created to close the duplicate issues: `scripts/triage-duplicate-issues.sh`
|
||||
|
||||
**To run the script:**
|
||||
**The script now dynamically finds and closes duplicates:**
|
||||
|
||||
```bash
|
||||
# Set your GitHub token (needs repo write access)
|
||||
export GITHUB_TOKEN="your_github_token_here"
|
||||
|
||||
# Run the script
|
||||
# Run the script (uses default search pattern)
|
||||
./scripts/triage-duplicate-issues.sh
|
||||
|
||||
# Or with a custom search pattern
|
||||
export SEARCH_TITLE="Your custom issue title pattern"
|
||||
./scripts/triage-duplicate-issues.sh
|
||||
```
|
||||
|
||||
The script will:
|
||||
1. Add an explanatory comment to each duplicate issue
|
||||
2. Close the issue with state_reason "not_planned"
|
||||
3. Keep issue #124 and #24 open
|
||||
**The script will:**
|
||||
1. Search for all open issues matching the title pattern using GitHub API
|
||||
2. Sort issues by creation date (newest first)
|
||||
3. Keep the most recent issue open
|
||||
4. Add an explanatory comment to each older duplicate issue
|
||||
5. Close duplicate issues with state_reason "not_planned"
|
||||
|
||||
**Key Features:**
|
||||
- ✅ Dynamic duplicate detection (no hardcoded issue numbers)
|
||||
- ✅ Automatically keeps the most recent issue open
|
||||
- ✅ Configurable search pattern via environment variable
|
||||
- ✅ Uses GitHub API search for accurate results
|
||||
|
||||
## Issues Closed
|
||||
|
||||
|
||||
@@ -51,14 +51,26 @@ rollback-preparation:
|
||||
### 2. Created Automation ✅
|
||||
|
||||
**Script:** `scripts/triage-duplicate-issues.sh`
|
||||
- Bulk-closes 21 duplicate issues (#92-#122)
|
||||
- Adds explanatory comment to each
|
||||
- Preserves issues #124 and #24
|
||||
- Dynamically finds duplicate issues using GitHub API
|
||||
- Sorts by creation date and identifies most recent issue
|
||||
- Bulk-closes all duplicates except the most recent one
|
||||
- Adds explanatory comment to each closed issue
|
||||
- Configurable via environment variables
|
||||
|
||||
**Features:**
|
||||
- ✅ No hardcoded issue numbers - uses API search
|
||||
- ✅ Automatically keeps most recent issue open
|
||||
- ✅ Customizable search pattern via `SEARCH_TITLE` env var
|
||||
- ✅ Comprehensive error handling and rate limiting
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
export GITHUB_TOKEN="your_token_with_repo_write_access"
|
||||
./scripts/triage-duplicate-issues.sh
|
||||
|
||||
# Or with custom search pattern:
|
||||
export SEARCH_TITLE="Custom Issue Title"
|
||||
./scripts/triage-duplicate-issues.sh
|
||||
```
|
||||
|
||||
### 3. Created Documentation ✅
|
||||
|
||||
168
scripts/test-triage-logic.sh
Executable file
168
scripts/test-triage-logic.sh
Executable file
@@ -0,0 +1,168 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Comprehensive test for triage-duplicate-issues.sh logic
|
||||
# This tests the core functions without making actual API calls
|
||||
|
||||
set -e
|
||||
|
||||
echo "🧪 Testing triage-duplicate-issues.sh logic"
|
||||
echo "============================================="
|
||||
echo ""
|
||||
|
||||
# Source the functions we need to test (extract them from the main script)
|
||||
# For testing, we'll recreate them here
|
||||
|
||||
get_issues_to_close() {
|
||||
local issues_data="$1"
|
||||
|
||||
if [ -z "$issues_data" ]; then
|
||||
echo "⚠️ No duplicate issues found" >&2
|
||||
return 0
|
||||
fi
|
||||
|
||||
local total_count=$(echo "$issues_data" | wc -l)
|
||||
|
||||
if [ "$total_count" -le 1 ]; then
|
||||
echo "ℹ️ Only one issue found, nothing to close" >&2
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Skip the first line (most recent issue) and get the rest
|
||||
echo "$issues_data" | tail -n +2 | cut -d'|' -f1
|
||||
}
|
||||
|
||||
# Test 1: Multiple duplicate issues
|
||||
echo "Test 1: Multiple duplicate issues (should close all except most recent)"
|
||||
echo "-----------------------------------------------------------------------"
|
||||
TEST_DATA_1='124|2025-12-27T10:30:00Z|🚨 Production Deployment Failed
|
||||
122|2025-12-27T10:25:00Z|🚨 Production Deployment Failed
|
||||
121|2025-12-27T10:20:00Z|🚨 Production Deployment Failed
|
||||
119|2025-12-27T10:15:00Z|🚨 Production Deployment Failed
|
||||
117|2025-12-27T10:10:00Z|🚨 Production Deployment Failed'
|
||||
|
||||
TOTAL=$(echo "$TEST_DATA_1" | wc -l)
|
||||
MOST_RECENT=$(echo "$TEST_DATA_1" | head -1 | cut -d'|' -f1)
|
||||
TO_CLOSE=$(get_issues_to_close "$TEST_DATA_1")
|
||||
TO_CLOSE_COUNT=$(echo "$TO_CLOSE" | wc -l)
|
||||
|
||||
echo " Total issues found: $TOTAL"
|
||||
echo " Most recent issue: #$MOST_RECENT"
|
||||
echo " Issues to close: $(echo $TO_CLOSE | tr '\n' ' ')"
|
||||
echo " Count to close: $TO_CLOSE_COUNT"
|
||||
|
||||
if [ "$MOST_RECENT" = "124" ] && [ "$TO_CLOSE_COUNT" = "4" ]; then
|
||||
echo " ✅ PASS: Correctly identified most recent and 4 issues to close"
|
||||
else
|
||||
echo " ❌ FAIL: Expected most recent=#124, count=4"
|
||||
exit 1
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Test 2: Two duplicate issues
|
||||
echo "Test 2: Two duplicate issues (should close oldest, keep newest)"
|
||||
echo "----------------------------------------------------------------"
|
||||
TEST_DATA_2='150|2025-12-27T11:00:00Z|Bug in login
|
||||
148|2025-12-27T10:55:00Z|Bug in login'
|
||||
|
||||
TOTAL=$(echo "$TEST_DATA_2" | wc -l)
|
||||
MOST_RECENT=$(echo "$TEST_DATA_2" | head -1 | cut -d'|' -f1)
|
||||
TO_CLOSE=$(get_issues_to_close "$TEST_DATA_2")
|
||||
TO_CLOSE_COUNT=$(echo "$TO_CLOSE" | wc -l)
|
||||
|
||||
echo " Total issues found: $TOTAL"
|
||||
echo " Most recent issue: #$MOST_RECENT"
|
||||
echo " Issues to close: $TO_CLOSE"
|
||||
echo " Count to close: $TO_CLOSE_COUNT"
|
||||
|
||||
if [ "$MOST_RECENT" = "150" ] && [ "$TO_CLOSE" = "148" ] && [ "$TO_CLOSE_COUNT" = "1" ]; then
|
||||
echo " ✅ PASS: Correctly keeps newest (#150) and closes oldest (#148)"
|
||||
else
|
||||
echo " ❌ FAIL: Expected most recent=#150, to_close=#148"
|
||||
exit 1
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Test 3: Single issue
|
||||
echo "Test 3: Single issue (should not close anything)"
|
||||
echo "-------------------------------------------------"
|
||||
TEST_DATA_3='200|2025-12-27T12:00:00Z|Unique issue'
|
||||
|
||||
TOTAL=$(echo "$TEST_DATA_3" | wc -l)
|
||||
MOST_RECENT=$(echo "$TEST_DATA_3" | head -1 | cut -d'|' -f1)
|
||||
TO_CLOSE=$(get_issues_to_close "$TEST_DATA_3" 2>&1)
|
||||
|
||||
echo " Total issues found: $TOTAL"
|
||||
echo " Most recent issue: #$MOST_RECENT"
|
||||
|
||||
if [ -z "$(echo "$TO_CLOSE" | grep -v "Only one issue")" ]; then
|
||||
echo " ✅ PASS: Correctly identified no issues to close (only 1 issue)"
|
||||
else
|
||||
echo " ❌ FAIL: Should not close anything with only 1 issue"
|
||||
exit 1
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Test 4: Empty input
|
||||
echo "Test 4: Empty input (should handle gracefully)"
|
||||
echo "----------------------------------------------"
|
||||
TO_CLOSE=$(get_issues_to_close "" 2>&1)
|
||||
|
||||
if [ -z "$(echo "$TO_CLOSE" | grep -v "No duplicate issues")" ]; then
|
||||
echo " ✅ PASS: Correctly handled empty input"
|
||||
else
|
||||
echo " ❌ FAIL: Should handle empty input gracefully"
|
||||
exit 1
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Test 5: Date parsing and sorting verification
|
||||
echo "Test 5: Verify sorting by creation date (newest first)"
|
||||
echo "-------------------------------------------------------"
|
||||
TEST_DATA_5='300|2025-12-27T15:00:00Z|Issue C
|
||||
299|2025-12-27T14:00:00Z|Issue B
|
||||
298|2025-12-27T13:00:00Z|Issue A'
|
||||
|
||||
MOST_RECENT=$(echo "$TEST_DATA_5" | head -1 | cut -d'|' -f1)
|
||||
MOST_RECENT_DATE=$(echo "$TEST_DATA_5" | head -1 | cut -d'|' -f2)
|
||||
OLDEST=$(echo "$TEST_DATA_5" | tail -1 | cut -d'|' -f1)
|
||||
|
||||
echo " Most recent: #$MOST_RECENT at $MOST_RECENT_DATE"
|
||||
echo " Oldest: #$OLDEST"
|
||||
|
||||
if [ "$MOST_RECENT" = "300" ] && [ "$OLDEST" = "298" ]; then
|
||||
echo " ✅ PASS: Correctly sorted by date (newest first)"
|
||||
else
|
||||
echo " ❌ FAIL: Sorting is incorrect"
|
||||
exit 1
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Test 6: jq parsing simulation (test data format)
|
||||
echo "Test 6: Verify data format compatibility with jq"
|
||||
echo "-------------------------------------------------"
|
||||
MOCK_JSON='{"items": [
|
||||
{"number": 124, "created_at": "2025-12-27T10:30:00Z", "title": "Test"},
|
||||
{"number": 122, "created_at": "2025-12-27T10:25:00Z", "title": "Test"}
|
||||
]}'
|
||||
|
||||
# Test that jq can parse and format the data correctly
|
||||
PARSED=$(echo "$MOCK_JSON" | jq -r '.items | sort_by(.created_at) | reverse | .[] | "\(.number)|\(.created_at)|\(.title)"')
|
||||
FIRST_ISSUE=$(echo "$PARSED" | head -1 | cut -d'|' -f1)
|
||||
|
||||
if [ "$FIRST_ISSUE" = "124" ]; then
|
||||
echo " ✅ PASS: jq parsing and formatting works correctly"
|
||||
else
|
||||
echo " ❌ FAIL: jq parsing failed"
|
||||
exit 1
|
||||
fi
|
||||
echo ""
|
||||
|
||||
echo "============================================="
|
||||
echo "✅ All tests passed!"
|
||||
echo ""
|
||||
echo "Summary:"
|
||||
echo " - Correctly identifies most recent issue"
|
||||
echo " - Closes all duplicates except the most recent"
|
||||
echo " - Handles edge cases (single issue, empty input)"
|
||||
echo " - Date sorting works correctly"
|
||||
echo " - Data format compatible with GitHub API response"
|
||||
@@ -1,53 +1,164 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Script to bulk-close duplicate "Production Deployment Failed" issues
|
||||
# These were created by a misconfigured workflow that triggered rollback issues
|
||||
# on pre-deployment validation failures rather than actual deployment failures.
|
||||
# Script to bulk-close duplicate issues found via GitHub API
|
||||
# Dynamically finds issues with duplicate titles and closes all except the most recent one
|
||||
#
|
||||
# Usage:
|
||||
# export GITHUB_TOKEN="ghp_your_token_here"
|
||||
# ./triage-duplicate-issues.sh
|
||||
#
|
||||
# Or with custom search pattern:
|
||||
# export GITHUB_TOKEN="ghp_your_token_here"
|
||||
# export SEARCH_TITLE="Custom Issue Title"
|
||||
# ./triage-duplicate-issues.sh
|
||||
#
|
||||
# The script will:
|
||||
# 1. Search for all open issues matching the SEARCH_TITLE pattern
|
||||
# 2. Sort them by creation date (newest first)
|
||||
# 3. Keep the most recent issue open
|
||||
# 4. Close all other duplicates with an explanatory comment
|
||||
|
||||
set -e
|
||||
|
||||
GITHUB_TOKEN="${GITHUB_TOKEN}"
|
||||
usage() {
|
||||
echo "Usage: $0"
|
||||
echo ""
|
||||
echo "Environment variables:"
|
||||
echo " GITHUB_TOKEN (required) GitHub personal access token with repo access"
|
||||
echo " SEARCH_TITLE (optional) Issue title pattern to search for"
|
||||
echo " Default: '🚨 Production Deployment Failed - Rollback Required'"
|
||||
echo ""
|
||||
echo "Example:"
|
||||
echo " export GITHUB_TOKEN='ghp_xxxxxxxxxxxx'"
|
||||
echo " export SEARCH_TITLE='Duplicate bug report'"
|
||||
echo " $0"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Check for help flag
|
||||
if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
|
||||
usage
|
||||
fi
|
||||
|
||||
if [ -z "$GITHUB_TOKEN" ]; then
|
||||
echo "❌ GITHUB_TOKEN environment variable is required"
|
||||
exit 1
|
||||
echo ""
|
||||
usage
|
||||
fi
|
||||
|
||||
OWNER="johndoe6345789"
|
||||
REPO="metabuilder"
|
||||
|
||||
# Issues to close - all the duplicate deployment failure issues except the most recent (#124)
|
||||
ISSUES_TO_CLOSE=(92 93 95 96 97 98 99 100 101 102 104 105 107 108 111 113 115 117 119 121 122)
|
||||
# Search pattern for duplicate issues (can be customized)
|
||||
SEARCH_TITLE="${SEARCH_TITLE:-🚨 Production Deployment Failed - Rollback Required}"
|
||||
|
||||
# Function to fetch issues by title pattern
|
||||
fetch_duplicate_issues() {
|
||||
local search_query="$1"
|
||||
echo "🔍 Searching for issues with title: \"$search_query\"" >&2
|
||||
|
||||
# Use GitHub API to search for issues by title
|
||||
# Filter by: is:issue, is:open, repo, and title match
|
||||
local encoded_query
|
||||
encoded_query=$(echo "is:issue is:open repo:$OWNER/$REPO in:title $search_query" | jq -sRr @uri)
|
||||
|
||||
local response
|
||||
response=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
"https://api.github.com/search/issues?q=$encoded_query&sort=created&order=desc&per_page=100")
|
||||
|
||||
# Check for API errors
|
||||
if echo "$response" | jq -e '.message' > /dev/null 2>&1; then
|
||||
local error_msg
|
||||
error_msg=$(echo "$response" | jq -r '.message')
|
||||
echo "❌ GitHub API error: $error_msg" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Extract issue numbers and creation dates, sorted by creation date (newest first)
|
||||
echo "$response" | jq -r '.items | sort_by(.created_at) | reverse | .[] | "\(.number)|\(.created_at)|\(.title)"'
|
||||
}
|
||||
|
||||
# Function to determine which issues to close (all except the most recent)
|
||||
get_issues_to_close() {
|
||||
local issues_data="$1"
|
||||
|
||||
if [ -z "$issues_data" ]; then
|
||||
echo "⚠️ No duplicate issues found" >&2
|
||||
return 0
|
||||
fi
|
||||
|
||||
local total_count
|
||||
total_count=$(echo "$issues_data" | wc -l)
|
||||
|
||||
if [ "$total_count" -le 1 ]; then
|
||||
echo "ℹ️ Only one issue found, nothing to close" >&2
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Skip the first line (most recent issue) and get the rest
|
||||
echo "$issues_data" | tail -n +2 | cut -d'|' -f1
|
||||
}
|
||||
|
||||
# Fetch all duplicate issues
|
||||
ISSUES_DATA=$(fetch_duplicate_issues "$SEARCH_TITLE")
|
||||
|
||||
if [ -z "$ISSUES_DATA" ]; then
|
||||
echo "✨ No duplicate issues found. Nothing to do!"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Parse the data
|
||||
TOTAL_ISSUES=$(echo "$ISSUES_DATA" | wc -l)
|
||||
MOST_RECENT=$(echo "$ISSUES_DATA" | head -1 | cut -d'|' -f1)
|
||||
MOST_RECENT_DATE=$(echo "$ISSUES_DATA" | head -1 | cut -d'|' -f2)
|
||||
|
||||
echo "📊 Found $TOTAL_ISSUES duplicate issues"
|
||||
echo "📌 Most recent issue: #$MOST_RECENT (created: $MOST_RECENT_DATE)"
|
||||
echo ""
|
||||
|
||||
# Get list of issues to close
|
||||
ISSUES_TO_CLOSE_DATA=$(get_issues_to_close "$ISSUES_DATA")
|
||||
|
||||
if [ -z "$ISSUES_TO_CLOSE_DATA" ]; then
|
||||
echo "✨ No issues need to be closed!"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Convert to array
|
||||
ISSUES_TO_CLOSE=()
|
||||
while IFS= read -r issue_num; do
|
||||
ISSUES_TO_CLOSE+=("$issue_num")
|
||||
done <<< "$ISSUES_TO_CLOSE_DATA"
|
||||
|
||||
CLOSE_COMMENT='🤖 **Automated Triage: Closing Duplicate Issue**
|
||||
|
||||
This issue was automatically created by a misconfigured workflow. The deployment workflow was creating "rollback required" issues when **pre-deployment validation** failed, not when actual deployments failed.
|
||||
|
||||
**Root Cause:**
|
||||
- The `rollback-preparation` job had `if: failure()` which triggered when ANY upstream job failed
|
||||
- It should have been `if: needs.deploy-production.result == '"'"'failure'"'"'` to only trigger on actual deployment failures
|
||||
This issue has been identified as a duplicate. Multiple issues with the same title were found, and this script automatically closes all duplicates except the most recent one.
|
||||
|
||||
**Resolution:**
|
||||
- ✅ Fixed the workflow in the latest commit
|
||||
- ✅ Keeping issue #124 as the canonical tracking issue
|
||||
- ✅ Closing this and other duplicate issues created by the same root cause
|
||||
- ✅ Keeping the most recent issue (#'"$MOST_RECENT"') as the canonical tracking issue
|
||||
- ✅ Closing this and other duplicate issues to maintain a clean issue tracker
|
||||
|
||||
**No Action Required** - These were false positives and no actual production deployments failed.
|
||||
**How duplicates were identified:**
|
||||
- Search pattern: "'"$SEARCH_TITLE"'"
|
||||
- Total duplicates found: '"$TOTAL_ISSUES"'
|
||||
- Keeping most recent: Issue #'"$MOST_RECENT"' (created '"$MOST_RECENT_DATE"')
|
||||
|
||||
**No Action Required** - Please refer to issue #'"$MOST_RECENT"' for continued discussion.
|
||||
|
||||
---
|
||||
*For questions about this automated triage, see the commit that fixed the workflow.*'
|
||||
*This closure was performed by an automated triage script. For questions, see `scripts/triage-duplicate-issues.sh`*'
|
||||
|
||||
close_issue() {
|
||||
local issue_number=$1
|
||||
|
||||
# Add comment explaining closure
|
||||
echo "📝 Adding comment to issue #${issue_number}..."
|
||||
curl -s -X POST \
|
||||
if curl -s -X POST \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
"https://api.github.com/repos/$OWNER/$REPO/issues/$issue_number/comments" \
|
||||
-d "{\"body\": $(echo "$CLOSE_COMMENT" | jq -Rs .)}" > /dev/null
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
-d "{\"body\": $(echo "$CLOSE_COMMENT" | jq -Rs .)}" > /dev/null; then
|
||||
echo "✅ Added comment to issue #${issue_number}"
|
||||
else
|
||||
echo "❌ Failed to add comment to issue #${issue_number}"
|
||||
@@ -56,13 +167,11 @@ close_issue() {
|
||||
|
||||
# Close the issue
|
||||
echo "🔒 Closing issue #${issue_number}..."
|
||||
curl -s -X PATCH \
|
||||
if curl -s -X PATCH \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
"https://api.github.com/repos/$OWNER/$REPO/issues/$issue_number" \
|
||||
-d '{"state": "closed", "state_reason": "not_planned"}' > /dev/null
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
-d '{"state": "closed", "state_reason": "not_planned"}' > /dev/null; then
|
||||
echo "✅ Closed issue #${issue_number}"
|
||||
else
|
||||
echo "❌ Failed to close issue #${issue_number}"
|
||||
@@ -76,6 +185,7 @@ main() {
|
||||
echo "🔧 Starting bulk issue triage..."
|
||||
echo ""
|
||||
echo "📋 Planning to close ${#ISSUES_TO_CLOSE[@]} duplicate issues"
|
||||
echo "📌 Keeping issue #$MOST_RECENT open (most recent)"
|
||||
echo ""
|
||||
|
||||
for issue_number in "${ISSUES_TO_CLOSE[@]}"; do
|
||||
@@ -86,9 +196,8 @@ main() {
|
||||
|
||||
echo "✨ Triage complete!"
|
||||
echo ""
|
||||
echo "📌 Keeping open:"
|
||||
echo " - Issue #124 (most recent deployment failure - canonical tracking issue)"
|
||||
echo " - Issue #24 (Renovate Dependency Dashboard - legitimate)"
|
||||
echo "📌 Kept open: Issue #$MOST_RECENT (most recent, created $MOST_RECENT_DATE)"
|
||||
echo "🔒 Closed: ${#ISSUES_TO_CLOSE[@]} duplicate issues"
|
||||
}
|
||||
|
||||
main
|
||||
|
||||
Reference in New Issue
Block a user