From 070b46e1ef2b5b43f04f4b1bdf79d4dc52a2cde3 Mon Sep 17 00:00:00 2001 From: Turtle Date: Wed, 2 Sep 2026 15:06:25 +0800 Subject: [PATCH] fix(issue-management): restore Project date fields --- .github/issue-management/config.json | 2 +- .github/issue-management/policy.mjs | 39 ++++++++--------- .github/issue-management/policy.test.mjs | 55 +++++++++++------------- 3 files changed, 43 insertions(+), 53 deletions(-) diff --git a/.github/issue-management/config.json b/.github/issue-management/config.json index 85fd4086bd..a935fb5ef8 100644 --- a/.github/issue-management/config.json +++ b/.github/issue-management/config.json @@ -5,7 +5,7 @@ "projectTitle": "DSH Issue Management", "lifecycleActor": "dsh-issue-management", "priorityField": "Priority", - "startDateField": "Start date", + "startDateField": "Start Date", "projectTimeZone": "Asia/Shanghai", "allowUnassignedOwner": true, "statuses": [ diff --git a/.github/issue-management/policy.mjs b/.github/issue-management/policy.mjs index 8b4cde7aab..ef5583894f 100644 --- a/.github/issue-management/policy.mjs +++ b/.github/issue-management/policy.mjs @@ -484,13 +484,7 @@ async function projectContext(number, includeStatusActor = false, includeStartDa title fields(first: 50) { nodes { - ... on ProjectV2Field { - id - name - dataType - isIssueField - issueField { ... on IssueFieldDate { id } } - } + ... on ProjectV2Field { id name dataType isIssueField } ... on ProjectV2SingleSelectField { id name dataType options { id name } } } } @@ -518,9 +512,7 @@ async function projectContext(number, includeStatusActor = false, includeStartDa } startDateValue: fieldValueByName(name: $startDateField) @include(if: $includeStartDate) { - ... on ProjectV2ItemIssueFieldValue { - issueFieldValue { ... on IssueFieldDateValue { value } } - } + ... on ProjectV2ItemFieldDateValue { date } } } } @@ -552,8 +544,8 @@ async function projectContext(number, includeStatusActor = false, includeStartDa if (startDateField && startDateField.dataType !== 'DATE') { throw new Error(`Project ${config.startDateField} 字段必须为 Date`) } - if (startDateField && (!startDateField.isIssueField || !startDateField.issueField?.id)) { - throw new Error(`Project ${config.startDateField} 字段必须为 Issue Date 字段`) + if (startDateField?.isIssueField) { + throw new Error(`Project ${config.startDateField} 字段必须为 Project Date 字段`) } const item = issue.projectItems.nodes.find((candidate) => candidate.project.id === project.id) const latestStatusEvent = issue.timelineItems?.nodes @@ -593,24 +585,27 @@ async function ensureProjectItem(number, includeStartDate = false) { } /** - * Initialize one Issue's organization Start date when it is empty. + * Initialize one Issue's Project Start Date when it is empty. * @param {number} number Same-repository Issue number. * @param {string} date Date in YYYY-MM-DD form. - * @returns {Promise} Resolves after the conditional Issue-field update. + * @returns {Promise} Resolves after the conditional Project update. */ export async function initializeIssueStartDate(number, date) { const context = await ensureProjectItem(number, true) - if (context.item.startDateValue?.issueFieldValue?.value) return + if (context.item.startDateValue?.date) return await graphql( - `mutation($issueId: ID!, $fieldId: ID!, $date: String!) { - updateIssueFieldValue(input: { - issueId: $issueId, - issueField: {fieldId: $fieldId, dateValue: $date} - }) { issue { id } } + `mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $date: Date!) { + updateProjectV2ItemFieldValue(input: { + projectId: $projectId, + itemId: $itemId, + fieldId: $fieldId, + value: {date: $date} + }) { projectV2Item { id } } }`, { - issueId: context.issue.id, - fieldId: context.startDateField.issueField.id, + projectId: context.project.id, + itemId: context.item.id, + fieldId: context.startDateField.id, date, }, ) diff --git a/.github/issue-management/policy.test.mjs b/.github/issue-management/policy.test.mjs index 3fc1071ada..252654ce57 100644 --- a/.github/issue-management/policy.test.mjs +++ b/.github/issue-management/policy.test.mjs @@ -21,8 +21,7 @@ const projectGraphqlData = ({ startDate = null, startDateField = true, startDateType = 'DATE', - startDateIsIssueField = true, - startDateIssueField = true, + startDateIsIssueField = false, } = {}) => ({ organization: { projectV2: { @@ -34,11 +33,10 @@ const projectGraphqlData = ({ ...(startDateField ? [ { - id: 'start-date-project-field-id', - name: 'Start date', + id: 'start-date-field-id', + name: 'Start Date', dataType: startDateType, isIssueField: startDateIsIssueField, - issueField: startDateIssueField ? { id: 'start-date-issue-field-id' } : null, }, ] : []), @@ -56,8 +54,7 @@ const projectGraphqlData = ({ id: 'item-id', project: { id: 'project-id' }, fieldValueByName: { name: 'Inbox', optionId: 'inbox-option-id' }, - startDateValue: - startDate === null ? null : { issueFieldValue: { value: startDate } }, + startDateValue: startDate === null ? null : { date: startDate }, }, ] : [], @@ -269,27 +266,29 @@ test('initializes every referenced Issue only for a PR opened event', async () = assert.equal(writes.length, 3) }) -test('writes an empty Issue Start date with the configured field', async (t) => { +test('writes an empty Project Start Date with the configured field', async (t) => { const requests = mockGraphql(t, (request) => { if (request.query.includes('query(')) return projectGraphqlData() - return { updateIssueFieldValue: { issue: { id: 'issue-id' } } } + return { updateProjectV2ItemFieldValue: { projectV2Item: { id: 'item-id' } } } }) await initializeIssueStartDate(42, '2026-08-28') assert.equal(requests.length, 2) assert.match(requests[0].query, /isIssueField/) - assert.match(requests[0].query, /ProjectV2ItemIssueFieldValue/) - assert.match(requests[1].query, /updateIssueFieldValue/) - assert.match(requests[1].query, /issueField: \{fieldId: \$fieldId, dateValue: \$date\}/) + assert.doesNotMatch(requests[0].query, /issueField\s*\{/) + assert.match(requests[0].query, /ProjectV2ItemFieldDateValue/) + assert.match(requests[1].query, /updateProjectV2ItemFieldValue/) + assert.match(requests[1].query, /value: \{date: \$date\}/) assert.deepEqual(requests[1].variables, { - issueId: 'issue-id', - fieldId: 'start-date-issue-field-id', + projectId: 'project-id', + itemId: 'item-id', + fieldId: 'start-date-field-id', date: '2026-08-28', }) }) -test('preserves an existing Issue Start date', async (t) => { +test('preserves an existing Project Start Date', async (t) => { const requests = mockGraphql(t, () => projectGraphqlData({ startDate: '2026-08-01' })) await initializeIssueStartDate(42, '2026-08-28') @@ -297,13 +296,13 @@ test('preserves an existing Issue Start date', async (t) => { assert.equal(requests.length, 1) }) -test('adds a referenced Issue to the Project before setting Start date', async (t) => { +test('adds a referenced Issue to the Project before setting Start Date', async (t) => { const requests = mockGraphql(t, (request) => { if (request.query.includes('query(')) return projectGraphqlData({ projectItem: false }) if (request.query.includes('addProjectV2ItemById')) { return { addProjectV2ItemById: { item: { id: 'new-item-id' } } } } - return { updateIssueFieldValue: { issue: { id: 'issue-id' } } } + return { updateProjectV2ItemFieldValue: { projectV2Item: { id: 'new-item-id' } } } }) await initializeIssueStartDate(42, '2026-08-28') @@ -311,30 +310,26 @@ test('adds a referenced Issue to the Project before setting Start date', async ( assert.equal(requests.length, 3) assert.deepEqual(requests[1].variables, { projectId: 'project-id', contentId: 'issue-id' }) assert.deepEqual(requests[2].variables, { - issueId: 'issue-id', - fieldId: 'start-date-issue-field-id', + projectId: 'project-id', + itemId: 'new-item-id', + fieldId: 'start-date-field-id', date: '2026-08-28', }) }) -test('rejects a missing, non-Date, or Project-local Start date field', async (t) => { +test('rejects a missing, non-Date, or Issue-level Start Date field', async (t) => { let response = projectGraphqlData({ startDateField: false }) const requests = mockGraphql(t, () => response) - await assert.rejects(initializeIssueStartDate(42, '2026-08-28'), /Project 缺少 Start date 字段/) + await assert.rejects(initializeIssueStartDate(42, '2026-08-28'), /Project 缺少 Start Date 字段/) response = projectGraphqlData({ startDateType: 'TEXT' }) - await assert.rejects(initializeIssueStartDate(42, '2026-08-28'), /Start date 字段必须为 Date/) - response = projectGraphqlData({ startDateIsIssueField: false }) + await assert.rejects(initializeIssueStartDate(42, '2026-08-28'), /Start Date 字段必须为 Date/) + response = projectGraphqlData({ startDateIsIssueField: true }) await assert.rejects( initializeIssueStartDate(42, '2026-08-28'), - /Start date 字段必须为 Issue Date 字段/, + /Start Date 字段必须为 Project Date 字段/, ) - response = projectGraphqlData({ startDateIssueField: false }) - await assert.rejects( - initializeIssueStartDate(42, '2026-08-28'), - /Start date 字段必须为 Issue Date 字段/, - ) - assert.equal(requests.length, 4) + assert.equal(requests.length, 3) }) test('does not treat pull request references as Issue associations', () => {