From 66237d716083499da6da3582219a31493a07d232 Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Thu, 11 Jun 2026 11:54:24 +0800 Subject: [PATCH] feat(project): set assigned issues in progress --- .github/scripts/sync-project-triage.mjs | 70 ++++++++++++++++++++++++- .github/workflows/project-triage.yml | 3 +- 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/.github/scripts/sync-project-triage.mjs b/.github/scripts/sync-project-triage.mjs index dd172265db..32827886cc 100644 --- a/.github/scripts/sync-project-triage.mjs +++ b/.github/scripts/sync-project-triage.mjs @@ -32,6 +32,7 @@ const repo = args.repo || process.env.GITHUB_REPOSITORY || DEFAULT_REPO; const [repoOwner, repoName] = repo.split("/"); const issueNumber = args["issue-number"] ? Number(args["issue-number"]) : null; const mode = args.backfill === "true" ? "backfill" : "issue"; +const eventAction = args["event-action"] || process.env.ISSUE_EVENT_ACTION || ""; if (!projectToken) { throw new Error("PROJECT_TOKEN, GH_TOKEN, or GITHUB_TOKEN is required"); @@ -89,6 +90,18 @@ function triageName(issue) { return "Inbox"; } +function singleSelectOptionsByName(field) { + return Object.fromEntries((field?.options || []).map((option) => [option.name, option.id])); +} + +function singleSelectOptionId(options, name) { + const exact = options[name]; + if (exact) return exact; + const target = name.toLowerCase(); + const entry = Object.entries(options).find(([optionName]) => optionName.toLowerCase() === target); + return entry?.[1]; +} + async function getProjectConfig() { const query = ` query { @@ -128,12 +141,15 @@ async function getProjectConfig() { if (!triageField) { throw new Error(`Project ${project.title} is missing a Triage field`); } + const statusField = project.fields.nodes.find((field) => field.name === "Status"); return { id: project.id, title: project.title, triageFieldId: triageField.id, - triageOptions: Object.fromEntries(triageField.options.map((option) => [option.name, option.id])), + triageOptions: singleSelectOptionsByName(triageField), + statusFieldId: statusField?.id || "", + statusOptions: singleSelectOptionsByName(statusField), }; } @@ -180,6 +196,12 @@ async function fetchIssue(number) { optionId } } + statusValue: fieldValueByName(name: "Status") { + ... on ProjectV2ItemFieldSingleSelectValue { + name + optionId + } + } } } } @@ -232,6 +254,27 @@ async function updateTriage({ projectId, itemId, fieldId, optionId }) { await graphql(mutation, projectToken); } +async function updateSingleSelectField({ projectId, itemId, fieldId, optionId }) { + const mutation = ` + mutation { + updateProjectV2ItemFieldValue( + input: { + projectId: ${gqlString(projectId)} + itemId: ${gqlString(itemId)} + fieldId: ${gqlString(fieldId)} + value: { singleSelectOptionId: ${gqlString(optionId)} } + } + ) { + projectV2Item { + id + } + } + } + `; + + await graphql(mutation, projectToken); +} + async function syncIssue(projectConfig, number) { const issue = await fetchIssue(number); const targetTriage = triageName(issue); @@ -251,6 +294,7 @@ async function syncIssue(projectConfig, number) { projectItem = { id: itemId, fieldValueByName: null, + statusValue: null, }; console.log(`Added issue #${issue.number} to ${projectConfig.title}`); } @@ -267,6 +311,30 @@ async function syncIssue(projectConfig, number) { } else { console.log(`Issue #${issue.number} triage already ${targetTriage}`); } + + if (eventAction === "assigned" && issue.state === "OPEN") { + const targetStatus = "In Progress"; + const statusOptionId = singleSelectOptionId(projectConfig.statusOptions, targetStatus); + if (!projectConfig.statusFieldId) { + throw new Error(`Project ${projectConfig.title} is missing a Status field`); + } + if (!statusOptionId) { + throw new Error(`Missing Status option: ${targetStatus}`); + } + + const currentStatusOptionId = projectItem.statusValue?.optionId || ""; + if (currentStatusOptionId !== statusOptionId) { + await updateSingleSelectField({ + projectId: projectConfig.id, + itemId: projectItem.id, + fieldId: projectConfig.statusFieldId, + optionId: statusOptionId, + }); + console.log(`Set issue #${issue.number} status to ${targetStatus}`); + } else { + console.log(`Issue #${issue.number} status already ${targetStatus}`); + } + } } async function fetchOpenIssueNumbers() { diff --git a/.github/workflows/project-triage.yml b/.github/workflows/project-triage.yml index 629bb58d2d..1e3b3171bc 100644 --- a/.github/workflows/project-triage.yml +++ b/.github/workflows/project-triage.yml @@ -40,7 +40,8 @@ jobs: if: ${{ github.event_name == 'issues' }} env: ISSUE_NUMBER: ${{ github.event.issue.number }} - run: node .github/scripts/sync-project-triage.mjs --issue-number "$ISSUE_NUMBER" + ISSUE_EVENT_ACTION: ${{ github.event.action }} + run: node .github/scripts/sync-project-triage.mjs --issue-number "$ISSUE_NUMBER" --event-action "$ISSUE_EVENT_ACTION" - name: Backfill open issues if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.backfill == 'true' }}