Sync Week from Features to Children #1383
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Sync Week from Features to Children | |
| on: | |
| # Manual trigger | |
| workflow_dispatch: | |
| # Run every 4 hours | |
| schedule: | |
| - cron: "0 */4 * * *" | |
| permissions: | |
| issues: write | |
| contents: read | |
| jobs: | |
| sync-week-to-children: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Sync Week field from Features to their children | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.ORG_PROJECT_TOKEN || secrets.GITHUB_TOKEN }} | |
| script: | | |
| const ORG = 'Commute-ai'; | |
| const PROJECT_NUM = 1; | |
| console.log('🔄 Starting Week sync from .github repo issues to their children...'); | |
| // Query to get all project items from .github repo with sub-issues | |
| const query = ` | |
| query($org: String!, $projNum: Int!) { | |
| organization(login: $org) { | |
| projectV2(number: $projNum) { | |
| id | |
| items(first: 100) { | |
| nodes { | |
| id | |
| content { | |
| ... on Issue { | |
| id | |
| number | |
| title | |
| repository { | |
| name | |
| owner { | |
| login | |
| } | |
| } | |
| subIssues(first: 50) { | |
| nodes { | |
| id | |
| number | |
| title | |
| repository { | |
| owner { | |
| login | |
| } | |
| name | |
| } | |
| } | |
| } | |
| } | |
| } | |
| fieldValues(first: 20) { | |
| nodes { | |
| ... on ProjectV2ItemFieldIterationValue { | |
| title | |
| iterationId | |
| field { | |
| ... on ProjectV2IterationField { | |
| id | |
| name | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| `; | |
| try { | |
| const data = await github.graphql(query, { | |
| org: ORG, | |
| projNum: PROJECT_NUM, | |
| headers: { | |
| 'GraphQL-Features': 'sub_issues' | |
| } | |
| }); | |
| const project = data.organization.projectV2; | |
| const allItems = project.items.nodes; | |
| // Filter for issues in .github repo that have sub-issues | |
| const parentIssues = allItems.filter(item => { | |
| return item.content?.repository?.name === '.github' && | |
| item.content?.subIssues?.nodes?.length > 0; | |
| }); | |
| console.log(`📋 Found ${parentIssues.length} parent issues in .github repo with sub-issues`); | |
| let totalSynced = 0; | |
| // Process each parent issue | |
| for (const parent of parentIssues) { | |
| const parentTitle = parent.content.title; | |
| const parentNumber = parent.content.number; | |
| console.log(`\n🎯 Processing parent issue: .github#${parentNumber} - ${parentTitle}`); | |
| console.log(` Found ${parent.content.subIssues.nodes.length} sub-issues`); | |
| // Get parent's Week iteration (including null/empty state) | |
| const parentWeek = parent.fieldValues.nodes.find( | |
| fv => fv.field?.name === 'Week' | |
| ); | |
| // Determine if parent has a Week set or is empty | |
| const hasParentWeek = parentWeek && parentWeek.iterationId; | |
| const parentWeekId = hasParentWeek ? parentWeek.iterationId : null; | |
| const parentWeekTitle = hasParentWeek ? parentWeek.title : 'No Week'; | |
| console.log(` 📅 Parent Week: ${parentWeekTitle}`); | |
| // Get the Week field ID for updates (need this even for clearing) | |
| let weekFieldId = null; | |
| if (parentWeek) { | |
| weekFieldId = parentWeek.field.id; | |
| } else { | |
| // If parent doesn't have Week field, find it from any item that has it | |
| for (const item of allItems) { | |
| const weekField = item.fieldValues.nodes.find( | |
| fv => fv.field?.name === 'Week' | |
| ); | |
| if (weekField) { | |
| weekFieldId = weekField.field.id; | |
| break; | |
| } | |
| } | |
| } | |
| if (!weekFieldId) { | |
| console.log(' ⚠️ Could not find Week field ID, skipping'); | |
| continue; | |
| } | |
| // Update each sub-issue | |
| for (const subIssue of parent.content.subIssues.nodes) { | |
| const subRepo = subIssue.repository.name; | |
| const subNumber = subIssue.number; | |
| console.log(` 📌 Syncing sub-issue: ${subRepo}#${subNumber}`); | |
| // Find sub-issue in project items | |
| const subItem = allItems.find( | |
| item => item.content?.id === subIssue.id | |
| ); | |
| if (!subItem) { | |
| console.log(` ⚠️ Sub-issue not in project, skipping`); | |
| continue; | |
| } | |
| // Check current Week of sub-issue | |
| const currentWeek = subItem.fieldValues.nodes.find( | |
| fv => fv.field?.name === 'Week' | |
| ); | |
| const currentWeekId = currentWeek?.iterationId || null; | |
| // Check if sub-issue already matches parent state | |
| if (currentWeekId === parentWeekId) { | |
| console.log(` ✅ Already synced to ${parentWeekTitle}`); | |
| continue; | |
| } | |
| // Update the Week field (set to specific iteration or clear it) | |
| try { | |
| let updateMutation; | |
| let variables; | |
| if (hasParentWeek) { | |
| // Set to specific iteration | |
| updateMutation = ` | |
| mutation($projId: ID!, $itemId: ID!, $fieldId: ID!, $iterId: String!) { | |
| updateProjectV2ItemFieldValue(input: { | |
| projectId: $projId | |
| itemId: $itemId | |
| fieldId: $fieldId | |
| value: { iterationId: $iterId } | |
| }) { | |
| projectV2Item { | |
| id | |
| } | |
| } | |
| } | |
| `; | |
| variables = { | |
| projId: project.id, | |
| itemId: subItem.id, | |
| fieldId: weekFieldId, | |
| iterId: parentWeekId | |
| }; | |
| } else { | |
| // Clear the field (set to null) | |
| updateMutation = ` | |
| mutation($projId: ID!, $itemId: ID!, $fieldId: ID!) { | |
| clearProjectV2ItemFieldValue(input: { | |
| projectId: $projId | |
| itemId: $itemId | |
| fieldId: $fieldId | |
| }) { | |
| projectV2Item { | |
| id | |
| } | |
| } | |
| } | |
| `; | |
| variables = { | |
| projId: project.id, | |
| itemId: subItem.id, | |
| fieldId: weekFieldId | |
| }; | |
| } | |
| await github.graphql(updateMutation, variables); | |
| console.log(` ✅ Updated to ${parentWeekTitle}`); | |
| totalSynced++; | |
| } catch (updateError) { | |
| console.log(` ❌ Failed to update: ${updateError.message}`); | |
| } | |
| } | |
| } | |
| console.log(`\n✅ Sync complete! Updated ${totalSynced} sub-issues.`); | |
| } catch (error) { | |
| console.error('❌ Error during sync:', error.message); | |
| throw error; | |
| } |