8594e175f1
- Harden docker/release workflow with manual release guardrails and concurrency - Add stale issue cleanup workflow (issues only) - Add project field sync workflow from issue labels - Add weekly triage report workflow - Add CODEOWNERS for automatic review routing
145 lines
5.3 KiB
YAML
145 lines
5.3 KiB
YAML
name: Sync Project Fields
|
|
|
|
on:
|
|
issues:
|
|
types: [opened, labeled, unlabeled, reopened]
|
|
|
|
permissions: {}
|
|
|
|
jobs:
|
|
sync-fields:
|
|
name: Sync Type/Priority fields from labels
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Sync fields
|
|
uses: actions/github-script@v8
|
|
with:
|
|
github-token: ${{ secrets.ADD_TO_PROJECT_PAT }}
|
|
script: |
|
|
const projectId = 'PVT_kwHOADH82s4BO2OT';
|
|
const issueNodeId = context.payload.issue.node_id;
|
|
const issueNumber = context.payload.issue.number;
|
|
const labels = (context.payload.issue.labels || []).map(l => l.name.toLowerCase());
|
|
|
|
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
|
|
const getProjectItem = async () => {
|
|
const data = await github.graphql(`
|
|
query($nodeId: ID!) {
|
|
node(id: $nodeId) {
|
|
... on Issue {
|
|
projectItems(first: 20) {
|
|
nodes {
|
|
id
|
|
project { id }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`, { nodeId: issueNodeId });
|
|
|
|
const items = data.node?.projectItems?.nodes || [];
|
|
return items.find(item => item.project.id === projectId) || null;
|
|
};
|
|
|
|
let projectItem = await getProjectItem();
|
|
|
|
// add-to-project may run in parallel; retry briefly before giving up
|
|
for (let i = 0; !projectItem && i < 6; i++) {
|
|
console.log(`Issue #${issueNumber} not in project yet. Retry ${i + 1}/6...`);
|
|
await sleep(10000);
|
|
projectItem = await getProjectItem();
|
|
}
|
|
|
|
if (!projectItem) {
|
|
console.log(`Issue #${issueNumber} is not in project board. Skipping field sync.`);
|
|
return;
|
|
}
|
|
|
|
const fieldsData = await github.graphql(`
|
|
query($projectId: ID!) {
|
|
node(id: $projectId) {
|
|
... on ProjectV2 {
|
|
fields(first: 50) {
|
|
nodes {
|
|
... on ProjectV2SingleSelectField {
|
|
id
|
|
name
|
|
options {
|
|
id
|
|
name
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`, { projectId });
|
|
|
|
const singleSelectFields = fieldsData.node?.fields?.nodes || [];
|
|
const byName = new Map(singleSelectFields.map(f => [f.name, f]));
|
|
|
|
const typeField = byName.get('Type');
|
|
const priorityField = byName.get('Priority');
|
|
|
|
if (!typeField && !priorityField) {
|
|
console.log('Neither Type nor Priority field found. Nothing to update.');
|
|
return;
|
|
}
|
|
|
|
const pickOptionId = (field, optionName) => {
|
|
if (!field || !optionName) return null;
|
|
const opt = (field.options || []).find(o => o.name.toLowerCase() === optionName.toLowerCase());
|
|
return opt?.id || null;
|
|
};
|
|
|
|
let typeName = null;
|
|
if (labels.includes('bug')) typeName = 'Bug';
|
|
else if (labels.includes('enhancement')) typeName = 'Feature';
|
|
else if (labels.includes('documentation')) typeName = 'Documentation';
|
|
|
|
let priorityName = null;
|
|
if (labels.includes('priority/high')) priorityName = 'High';
|
|
else if (labels.includes('priority/low')) priorityName = 'Low';
|
|
else if (labels.includes('priority/medium')) priorityName = 'Medium';
|
|
else if (labels.includes('triage')) priorityName = 'Medium';
|
|
|
|
const updates = [];
|
|
const typeOptionId = pickOptionId(typeField, typeName);
|
|
if (typeField && typeOptionId) {
|
|
updates.push({ fieldId: typeField.id, optionId: typeOptionId, fieldName: 'Type', valueName: typeName });
|
|
}
|
|
|
|
const priorityOptionId = pickOptionId(priorityField, priorityName);
|
|
if (priorityField && priorityOptionId) {
|
|
updates.push({ fieldId: priorityField.id, optionId: priorityOptionId, fieldName: 'Priority', valueName: priorityName });
|
|
}
|
|
|
|
for (const update of updates) {
|
|
await github.graphql(`
|
|
mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) {
|
|
updateProjectV2ItemFieldValue(input: {
|
|
projectId: $projectId
|
|
itemId: $itemId
|
|
fieldId: $fieldId
|
|
value: { singleSelectOptionId: $optionId }
|
|
}) {
|
|
projectV2Item { id }
|
|
}
|
|
}
|
|
`, {
|
|
projectId,
|
|
itemId: projectItem.id,
|
|
fieldId: update.fieldId,
|
|
optionId: update.optionId
|
|
});
|
|
|
|
console.log(`Issue #${issueNumber}: set ${update.fieldName} = ${update.valueName}`);
|
|
}
|
|
|
|
if (updates.length === 0) {
|
|
console.log(`Issue #${issueNumber}: no matching field updates for labels [${labels.join(', ')}]`);
|
|
}
|