Files
AdventureLog/.github/workflows/sync-project-status.yml

168 lines
5.7 KiB
YAML

name: Sync Project Status
on:
issues:
types: [labeled]
jobs:
update-project:
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
repository-projects: write
steps:
- name: Update project status from label
uses: actions/github-script@v7
with:
github-token: ${{ secrets.PROJECT_AUTOMATION_TOKEN != '' && secrets.PROJECT_AUTOMATION_TOKEN || github.token }}
script: |
const labelMap = {
"backlog": "Backlog",
"needs discussion": "Needs discussion",
"approved": "Approved",
"ready": "Ready",
"in progress": "In progress",
"in review": "In review",
"done": "Done"
};
const label = context.payload.label.name.toLowerCase();
const targetOptionName = labelMap[label];
if (!targetOptionName) return;
const issueNodeId = context.payload.issue.node_id;
const configuredProjectId = "PVT_kwHOBeIeKs4AfmUO";
const fieldId = "PVTSSF_lAHOBeIeKs4AfmUOzgU5pCI";
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
async function fetchIssueProjectItems() {
const result = await github.graphql(`
query($issueId: ID!) {
node(id: $issueId) {
... on Issue {
projectItems(first: 100) {
nodes {
id
project {
id
title
}
}
}
}
}
}
`, {
issueId: issueNodeId
});
return result.node?.projectItems?.nodes ?? [];
}
// Retry a few times in case project membership is still syncing.
let issueProjectItems = [];
for (let attempt = 1; attempt <= 4; attempt++) {
issueProjectItems = await fetchIssueProjectItems();
if (issueProjectItems.length > 0) break;
if (attempt < 4) await sleep(3000);
}
let exactMatch = issueProjectItems.find(
(node) => node.project?.id === configuredProjectId
);
let itemId = exactMatch?.id;
let projectId = exactMatch?.project?.id;
// If issue is not in the configured project yet, try to add it.
if (!itemId) {
try {
const added = await github.graphql(`
mutation($projectId: ID!, $contentId: ID!) {
addProjectV2ItemById(input: { projectId: $projectId, contentId: $contentId }) {
item {
id
}
}
}
`, {
projectId: configuredProjectId,
contentId: issueNodeId
});
itemId = added.addProjectV2ItemById?.item?.id;
projectId = configuredProjectId;
} catch (error) {
const availableProjects = issueProjectItems
.map((node) => `${node.project?.title ?? "(untitled)"} [${node.project?.id ?? "no-id"}]`)
.join(", ");
console.log(`Issue not in configured project ${configuredProjectId}. Available: ${availableProjects || "none"}`);
console.log(`Failed to auto-add issue to configured project: ${error.message}`);
return;
}
}
if (!itemId || !projectId) {
const availableProjects = issueProjectItems
.map((node) => `${node.project?.title ?? "(untitled)"} [${node.project?.id ?? "no-id"}]`)
.join(", ");
console.log(`Issue not in configured project ${configuredProjectId}. Available: ${availableProjects || "none"}`);
return;
}
const fieldResult = await github.graphql(`
query($fieldId: ID!) {
node(id: $fieldId) {
... on ProjectV2SingleSelectField {
id
options {
id
name
}
}
}
}
`, {
fieldId: fieldId
});
const options = fieldResult.node?.options ?? [];
const selectedOption = options.find(
(option) => option.name?.toLowerCase() === targetOptionName.toLowerCase()
);
if (!selectedOption?.id) {
const availableOptions = options.map((option) => option.name).join(", ");
console.log(`Could not find option '${targetOptionName}' for field ${fieldId}. Available options: ${availableOptions || "none"}`);
return;
}
// update status field
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: projectId,
itemId: itemId,
fieldId: fieldId,
optionId: selectedOption.id
});