sync-project-priorities.shโข13.8 kB
#!/bin/bash
# Enhanced script to automatically discover and update GitHub Project priority fields
# This script:
# 1. Automatically finds your project and priority field
# 2. Updates priority field option names from P0/P1/P2 to emoji-based names
# 3. Syncs issues with priority labels to the project priority field
set -e
# Colors
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m'
echo -e "${BLUE}=== GitHub Project Priority Sync Tool ===${NC}"
echo ""
# Validate permissions before proceeding
validate_permissions() {
if ! gh auth status --hostname github.com 2>/dev/null | grep -q "project"; then
echo -e "${RED}Error: Missing project permissions${NC}"
echo "Run: gh auth refresh -s project"
exit 1
fi
}
# Check permissions first
validate_permissions
# Get current user and repo info
OWNER=$(gh api user --jq .login)
REPO_OWNER=$(gh repo view --json owner --jq .owner.login)
REPO_NAME=$(gh repo view --json name --jq .name)
echo -e "${GREEN}Repository: $REPO_OWNER/$REPO_NAME${NC}"
echo -e "${GREEN}User: $OWNER${NC}"
echo ""
# Function to list user's projects
list_projects() {
echo -e "${YELLOW}Fetching your projects...${NC}"
PROJECTS=$(gh api graphql -f query='
query($login: String!) {
user(login: $login) {
projectsV2(first: 20) {
nodes {
id
title
number
}
}
}
}
' -f login="$OWNER" --jq '.data.user.projectsV2.nodes[]')
if [ -z "$PROJECTS" ]; then
echo -e "${RED}No projects found${NC}"
return 1
fi
echo -e "${CYAN}Your projects:${NC}"
echo "$PROJECTS" | jq -r '"\(.number). \(.title) (ID: \(.id))"'
}
# Function to get project fields
get_project_fields() {
local PROJECT_ID=$1
FIELDS=$(gh api graphql -f query='
query($projectId: ID!) {
node(id: $projectId) {
... on ProjectV2 {
fields(first: 50) {
nodes {
... on ProjectV2Field {
id
name
dataType
}
... on ProjectV2SingleSelectField {
id
name
dataType
options {
id
name
color
}
}
}
}
}
}
}
' -f projectId="$PROJECT_ID" --jq '.data.node.fields.nodes[] | select(.dataType == "SINGLE_SELECT")')
echo "$FIELDS"
}
# Function to find priority field
find_priority_field() {
local PROJECT_ID=$1
echo -e "${YELLOW}Looking for Priority field...${NC}"
PRIORITY_FIELD=$(gh api graphql -f query='
query($projectId: ID!) {
node(id: $projectId) {
... on ProjectV2 {
fields(first: 50) {
nodes {
... on ProjectV2SingleSelectField {
id
name
dataType
options {
id
name
color
}
}
}
}
}
}
}
' -f projectId="$PROJECT_ID" --jq '.data.node.fields.nodes[] | select(.name == "Priority" or .name == "priority" or (.options[]?.name | test("P[0-2]|High|Medium|Low"; "i")))')
if [ -z "$PRIORITY_FIELD" ]; then
echo -e "${RED}No Priority field found${NC}"
return 1
fi
echo "$PRIORITY_FIELD" | jq -r '"Found field: \(.name) (ID: \(.id))"'
echo "$PRIORITY_FIELD"
}
# Function to update field options
update_priority_options() {
local PROJECT_ID=$1
local FIELD_ID=$2
local FIELD_DATA=$3
echo -e "\n${YELLOW}Current priority options:${NC}"
echo "$FIELD_DATA" | jq -r '.options[] | " \(.name) (ID: \(.id), Color: \(.color))"'
# Extract option IDs
local OPTIONS=$(echo "$FIELD_DATA" | jq -r '.options[]')
# Try to identify P0/P1/P2 or High/Medium/Low options
local HIGH_OPTION=""
local MEDIUM_OPTION=""
local LOW_OPTION=""
while IFS= read -r option; do
local NAME=$(echo "$option" | jq -r '.name')
local ID=$(echo "$option" | jq -r '.id')
# Match patterns for high/medium/low priorities
if [[ "$NAME" =~ P0|Critical|Urgent|High ]] || [[ "$NAME" == "High" ]]; then
HIGH_OPTION=$ID
elif [[ "$NAME" =~ P1|Medium ]] || [[ "$NAME" == "Medium" ]]; then
MEDIUM_OPTION=$ID
elif [[ "$NAME" =~ P2|Low ]] || [[ "$NAME" == "Low" ]]; then
LOW_OPTION=$ID
fi
done <<< "$OPTIONS"
echo -e "\n${CYAN}Detected mappings:${NC}"
[ -n "$HIGH_OPTION" ] && echo " High Priority: ID $HIGH_OPTION"
[ -n "$MEDIUM_OPTION" ] && echo " Medium Priority: ID $MEDIUM_OPTION"
[ -n "$LOW_OPTION" ] && echo " Low Priority: ID $LOW_OPTION"
echo -e "\n${YELLOW}Update option names? (y/n)${NC}"
read -p "> " confirm
if [ "$confirm" != "y" ]; then
return 0
fi
# Build options array for mutation
local OPTIONS_JSON="["
local FIRST=true
if [ -n "$HIGH_OPTION" ]; then
[ "$FIRST" = true ] && FIRST=false || OPTIONS_JSON+=","
OPTIONS_JSON+="{\"id\": \"$HIGH_OPTION\", \"name\": \"๐ด High\", \"color\": \"RED\"}"
fi
if [ -n "$MEDIUM_OPTION" ]; then
[ "$FIRST" = true ] && FIRST=false || OPTIONS_JSON+=","
OPTIONS_JSON+="{\"id\": \"$MEDIUM_OPTION\", \"name\": \"๐ก Medium\", \"color\": \"YELLOW\"}"
fi
if [ -n "$LOW_OPTION" ]; then
[ "$FIRST" = true ] && FIRST=false || OPTIONS_JSON+=","
OPTIONS_JSON+="{\"id\": \"$LOW_OPTION\", \"name\": \"๐ข Low\", \"color\": \"GREEN\"}"
fi
OPTIONS_JSON+="]"
echo -e "\n${YELLOW}Updating field options...${NC}"
RESULT=$(gh api graphql -f query='
mutation($projectId: ID!, $fieldId: ID!, $options: [ProjectV2SingleSelectFieldOptionInput!]!) {
updateProjectV2SingleSelectField(
input: {
projectId: $projectId
fieldId: $fieldId
options: $options
}
) {
projectV2Field {
... on ProjectV2SingleSelectField {
id
name
options {
id
name
color
}
}
}
}
}
' -f projectId="$PROJECT_ID" -f fieldId="$FIELD_ID" -f options="$OPTIONS_JSON" 2>&1)
if [ $? -eq 0 ]; then
echo -e "${GREEN}โ Field options updated successfully!${NC}"
echo "$RESULT" | jq -r '.data.updateProjectV2SingleSelectField.projectV2Field.options[] | " \(.name) (Color: \(.color))"'
else
echo -e "${RED}โ Error updating field options${NC}"
echo "$RESULT"
return 1
fi
# Return the option IDs for syncing
echo "$HIGH_OPTION|$MEDIUM_OPTION|$LOW_OPTION"
}
# Function to sync issues
sync_issues_to_project() {
local PROJECT_ID=$1
local FIELD_ID=$2
local HIGH_ID=$3
local MEDIUM_ID=$4
local LOW_ID=$5
echo -e "\n${YELLOW}Syncing issues with priority labels...${NC}"
# Process each priority level
for priority in "high:$HIGH_ID:๐ด High" "medium:$MEDIUM_ID:๐ก Medium" "low:$LOW_ID:๐ข Low"; do
IFS=':' read -r LABEL OPTION_ID DISPLAY_NAME <<< "$priority"
if [ -z "$OPTION_ID" ] || [ "$OPTION_ID" = "null" ]; then
echo -e "\n${YELLOW}Skipping $DISPLAY_NAME (no option ID)${NC}"
continue
fi
echo -e "\n${BLUE}Processing $DISPLAY_NAME issues...${NC}"
# Get issues with this priority label
ISSUES=$(gh issue list --label "priority: $LABEL" --limit 100 --json number,title,state --jq '.[] | select(.state == "OPEN")')
if [ -z "$ISSUES" ]; then
echo " No open issues with label 'priority: $LABEL'"
continue
fi
local COUNT=0
local SUCCESS=0
echo "$ISSUES" | while IFS= read -r issue; do
NUMBER=$(echo "$issue" | jq -r .number)
TITLE=$(echo "$issue" | jq -r .title)
COUNT=$((COUNT + 1))
echo -n " Issue #$NUMBER: ${TITLE:0:50}... "
# Get issue node ID
ISSUE_NODE_ID=$(gh api repos/$REPO_OWNER/$REPO_NAME/issues/$NUMBER --jq .node_id 2>/dev/null)
if [ -z "$ISSUE_NODE_ID" ]; then
echo -e "${RED}โ (couldn't get issue ID)${NC}"
continue
fi
# Check if issue is in project and get item ID
ITEM_DATA=$(gh api graphql -f query='
query($projectId: ID!, $issueId: ID!) {
node(id: $projectId) {
... on ProjectV2 {
items(first: 100) {
nodes {
id
content {
... on Issue {
id
}
}
}
}
}
}
}
' -f projectId="$PROJECT_ID" -f issueId="$ISSUE_NODE_ID" 2>/dev/null)
ITEM_ID=$(echo "$ITEM_DATA" | jq -r ".data.node.items.nodes[] | select(.content.id == \"$ISSUE_NODE_ID\") | .id" 2>/dev/null)
if [ -z "$ITEM_ID" ] || [ "$ITEM_ID" = "null" ]; then
# Add to project first
ADD_RESULT=$(gh api graphql -f query='
mutation($projectId: ID!, $contentId: ID!) {
addProjectV2ItemById(input: {projectId: $projectId, contentId: $contentId}) {
item {
id
}
}
}
' -f projectId="$PROJECT_ID" -f contentId="$ISSUE_NODE_ID" 2>/dev/null)
ITEM_ID=$(echo "$ADD_RESULT" | jq -r '.data.addProjectV2ItemById.item.id' 2>/dev/null)
if [ -z "$ITEM_ID" ] || [ "$ITEM_ID" = "null" ]; then
echo -e "${YELLOW}โ (couldn't add to project)${NC}"
continue
fi
fi
# Update priority field
UPDATE_RESULT=$(gh api graphql -f query='
mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $value: String!) {
updateProjectV2ItemFieldValue(
input: {
projectId: $projectId
itemId: $itemId
fieldId: $fieldId
value: {
singleSelectOptionId: $value
}
}
) {
projectV2Item {
id
}
}
}
' -f projectId="$PROJECT_ID" -f itemId="$ITEM_ID" -f fieldId="$FIELD_ID" -f value="$OPTION_ID" 2>&1)
if [ $? -eq 0 ]; then
echo -e "${GREEN}โ${NC}"
SUCCESS=$((SUCCESS + 1))
else
echo -e "${RED}โ${NC}"
fi
done
echo " Processed $COUNT issues, $SUCCESS updated successfully"
done
echo -e "\n${GREEN}Sync complete!${NC}"
}
# Main execution
main() {
# Step 1: List and select project
list_projects
echo ""
read -p "Enter project number: " PROJECT_NUM
# Validate project number
if ! [[ "$PROJECT_NUM" =~ ^[0-9]+$ ]]; then
echo -e "${RED}Error: Project number must be a positive integer${NC}"
exit 1
fi
# Get project ID
PROJECT_ID=$(gh api graphql -f query='
query($login: String!, $number: Int!) {
user(login: $login) {
projectV2(number: $number) {
id
title
}
}
}
' -f login="$OWNER" -F number=$PROJECT_NUM --jq '.data.user.projectV2.id')
if [ -z "$PROJECT_ID" ] || [ "$PROJECT_ID" = "null" ]; then
echo -e "${RED}Project not found${NC}"
exit 1
fi
PROJECT_TITLE=$(gh api graphql -f query='
query($login: String!, $number: Int!) {
user(login: $login) {
projectV2(number: $number) {
title
}
}
}
' -f login="$OWNER" -F number=$PROJECT_NUM --jq '.data.user.projectV2.title')
echo -e "${GREEN}Selected project: $PROJECT_TITLE${NC}"
# Step 2: Find priority field
FIELD_DATA=$(find_priority_field "$PROJECT_ID")
if [ -z "$FIELD_DATA" ]; then
echo -e "${RED}No priority field found in this project${NC}"
exit 1
fi
FIELD_ID=$(echo "$FIELD_DATA" | jq -r '.id' | head -n1)
# Step 3: Update field options
echo -e "\n${CYAN}Step 1: Update Priority Field Options${NC}"
OPTION_IDS=$(update_priority_options "$PROJECT_ID" "$FIELD_ID" "$FIELD_DATA" | tail -n1)
# Parse option IDs
IFS='|' read -r HIGH_ID MEDIUM_ID LOW_ID <<< "$OPTION_IDS"
# Step 4: Sync issues
echo -e "\n${CYAN}Step 2: Sync Issues with Priority Labels${NC}"
read -p "Sync issues now? (y/n): " sync_confirm
if [ "$sync_confirm" = "y" ]; then
sync_issues_to_project "$PROJECT_ID" "$FIELD_ID" "$HIGH_ID" "$MEDIUM_ID" "$LOW_ID"
fi
echo -e "\n${GREEN}All done!${NC}"
echo -e "View your project at: https://github.com/users/$OWNER/projects/$PROJECT_NUM"
}
# Run main function
main