Skip to content

Sync Week from Features to Children #1379

Sync Week from Features to Children

Sync Week from Features to Children #1379

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;
}