fix(issue-management): restore Project date fields
This commit is contained in:
parent
be70505f9e
commit
070b46e1ef
3 changed files with 43 additions and 53 deletions
2
.github/issue-management/config.json
vendored
2
.github/issue-management/config.json
vendored
|
|
@ -5,7 +5,7 @@
|
||||||
"projectTitle": "DSH Issue Management",
|
"projectTitle": "DSH Issue Management",
|
||||||
"lifecycleActor": "dsh-issue-management",
|
"lifecycleActor": "dsh-issue-management",
|
||||||
"priorityField": "Priority",
|
"priorityField": "Priority",
|
||||||
"startDateField": "Start date",
|
"startDateField": "Start Date",
|
||||||
"projectTimeZone": "Asia/Shanghai",
|
"projectTimeZone": "Asia/Shanghai",
|
||||||
"allowUnassignedOwner": true,
|
"allowUnassignedOwner": true,
|
||||||
"statuses": [
|
"statuses": [
|
||||||
|
|
|
||||||
39
.github/issue-management/policy.mjs
vendored
39
.github/issue-management/policy.mjs
vendored
|
|
@ -484,13 +484,7 @@ async function projectContext(number, includeStatusActor = false, includeStartDa
|
||||||
title
|
title
|
||||||
fields(first: 50) {
|
fields(first: 50) {
|
||||||
nodes {
|
nodes {
|
||||||
... on ProjectV2Field {
|
... on ProjectV2Field { id name dataType isIssueField }
|
||||||
id
|
|
||||||
name
|
|
||||||
dataType
|
|
||||||
isIssueField
|
|
||||||
issueField { ... on IssueFieldDate { id } }
|
|
||||||
}
|
|
||||||
... on ProjectV2SingleSelectField { id name dataType options { id name } }
|
... on ProjectV2SingleSelectField { id name dataType options { id name } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -518,9 +512,7 @@ async function projectContext(number, includeStatusActor = false, includeStartDa
|
||||||
}
|
}
|
||||||
startDateValue: fieldValueByName(name: $startDateField)
|
startDateValue: fieldValueByName(name: $startDateField)
|
||||||
@include(if: $includeStartDate) {
|
@include(if: $includeStartDate) {
|
||||||
... on ProjectV2ItemIssueFieldValue {
|
... on ProjectV2ItemFieldDateValue { date }
|
||||||
issueFieldValue { ... on IssueFieldDateValue { value } }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -552,8 +544,8 @@ async function projectContext(number, includeStatusActor = false, includeStartDa
|
||||||
if (startDateField && startDateField.dataType !== 'DATE') {
|
if (startDateField && startDateField.dataType !== 'DATE') {
|
||||||
throw new Error(`Project ${config.startDateField} 字段必须为 Date`)
|
throw new Error(`Project ${config.startDateField} 字段必须为 Date`)
|
||||||
}
|
}
|
||||||
if (startDateField && (!startDateField.isIssueField || !startDateField.issueField?.id)) {
|
if (startDateField?.isIssueField) {
|
||||||
throw new Error(`Project ${config.startDateField} 字段必须为 Issue Date 字段`)
|
throw new Error(`Project ${config.startDateField} 字段必须为 Project Date 字段`)
|
||||||
}
|
}
|
||||||
const item = issue.projectItems.nodes.find((candidate) => candidate.project.id === project.id)
|
const item = issue.projectItems.nodes.find((candidate) => candidate.project.id === project.id)
|
||||||
const latestStatusEvent = issue.timelineItems?.nodes
|
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 {number} number Same-repository Issue number.
|
||||||
* @param {string} date Date in YYYY-MM-DD form.
|
* @param {string} date Date in YYYY-MM-DD form.
|
||||||
* @returns {Promise<void>} Resolves after the conditional Issue-field update.
|
* @returns {Promise<void>} Resolves after the conditional Project update.
|
||||||
*/
|
*/
|
||||||
export async function initializeIssueStartDate(number, date) {
|
export async function initializeIssueStartDate(number, date) {
|
||||||
const context = await ensureProjectItem(number, true)
|
const context = await ensureProjectItem(number, true)
|
||||||
if (context.item.startDateValue?.issueFieldValue?.value) return
|
if (context.item.startDateValue?.date) return
|
||||||
await graphql(
|
await graphql(
|
||||||
`mutation($issueId: ID!, $fieldId: ID!, $date: String!) {
|
`mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $date: Date!) {
|
||||||
updateIssueFieldValue(input: {
|
updateProjectV2ItemFieldValue(input: {
|
||||||
issueId: $issueId,
|
projectId: $projectId,
|
||||||
issueField: {fieldId: $fieldId, dateValue: $date}
|
itemId: $itemId,
|
||||||
}) { issue { id } }
|
fieldId: $fieldId,
|
||||||
|
value: {date: $date}
|
||||||
|
}) { projectV2Item { id } }
|
||||||
}`,
|
}`,
|
||||||
{
|
{
|
||||||
issueId: context.issue.id,
|
projectId: context.project.id,
|
||||||
fieldId: context.startDateField.issueField.id,
|
itemId: context.item.id,
|
||||||
|
fieldId: context.startDateField.id,
|
||||||
date,
|
date,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
||||||
55
.github/issue-management/policy.test.mjs
vendored
55
.github/issue-management/policy.test.mjs
vendored
|
|
@ -21,8 +21,7 @@ const projectGraphqlData = ({
|
||||||
startDate = null,
|
startDate = null,
|
||||||
startDateField = true,
|
startDateField = true,
|
||||||
startDateType = 'DATE',
|
startDateType = 'DATE',
|
||||||
startDateIsIssueField = true,
|
startDateIsIssueField = false,
|
||||||
startDateIssueField = true,
|
|
||||||
} = {}) => ({
|
} = {}) => ({
|
||||||
organization: {
|
organization: {
|
||||||
projectV2: {
|
projectV2: {
|
||||||
|
|
@ -34,11 +33,10 @@ const projectGraphqlData = ({
|
||||||
...(startDateField
|
...(startDateField
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
id: 'start-date-project-field-id',
|
id: 'start-date-field-id',
|
||||||
name: 'Start date',
|
name: 'Start Date',
|
||||||
dataType: startDateType,
|
dataType: startDateType,
|
||||||
isIssueField: startDateIsIssueField,
|
isIssueField: startDateIsIssueField,
|
||||||
issueField: startDateIssueField ? { id: 'start-date-issue-field-id' } : null,
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
|
|
@ -56,8 +54,7 @@ const projectGraphqlData = ({
|
||||||
id: 'item-id',
|
id: 'item-id',
|
||||||
project: { id: 'project-id' },
|
project: { id: 'project-id' },
|
||||||
fieldValueByName: { name: 'Inbox', optionId: 'inbox-option-id' },
|
fieldValueByName: { name: 'Inbox', optionId: 'inbox-option-id' },
|
||||||
startDateValue:
|
startDateValue: startDate === null ? null : { date: startDate },
|
||||||
startDate === null ? null : { issueFieldValue: { value: startDate } },
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
: [],
|
: [],
|
||||||
|
|
@ -269,27 +266,29 @@ test('initializes every referenced Issue only for a PR opened event', async () =
|
||||||
assert.equal(writes.length, 3)
|
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) => {
|
const requests = mockGraphql(t, (request) => {
|
||||||
if (request.query.includes('query(')) return projectGraphqlData()
|
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')
|
await initializeIssueStartDate(42, '2026-08-28')
|
||||||
|
|
||||||
assert.equal(requests.length, 2)
|
assert.equal(requests.length, 2)
|
||||||
assert.match(requests[0].query, /isIssueField/)
|
assert.match(requests[0].query, /isIssueField/)
|
||||||
assert.match(requests[0].query, /ProjectV2ItemIssueFieldValue/)
|
assert.doesNotMatch(requests[0].query, /issueField\s*\{/)
|
||||||
assert.match(requests[1].query, /updateIssueFieldValue/)
|
assert.match(requests[0].query, /ProjectV2ItemFieldDateValue/)
|
||||||
assert.match(requests[1].query, /issueField: \{fieldId: \$fieldId, dateValue: \$date\}/)
|
assert.match(requests[1].query, /updateProjectV2ItemFieldValue/)
|
||||||
|
assert.match(requests[1].query, /value: \{date: \$date\}/)
|
||||||
assert.deepEqual(requests[1].variables, {
|
assert.deepEqual(requests[1].variables, {
|
||||||
issueId: 'issue-id',
|
projectId: 'project-id',
|
||||||
fieldId: 'start-date-issue-field-id',
|
itemId: 'item-id',
|
||||||
|
fieldId: 'start-date-field-id',
|
||||||
date: '2026-08-28',
|
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' }))
|
const requests = mockGraphql(t, () => projectGraphqlData({ startDate: '2026-08-01' }))
|
||||||
|
|
||||||
await initializeIssueStartDate(42, '2026-08-28')
|
await initializeIssueStartDate(42, '2026-08-28')
|
||||||
|
|
@ -297,13 +296,13 @@ test('preserves an existing Issue Start date', async (t) => {
|
||||||
assert.equal(requests.length, 1)
|
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) => {
|
const requests = mockGraphql(t, (request) => {
|
||||||
if (request.query.includes('query(')) return projectGraphqlData({ projectItem: false })
|
if (request.query.includes('query(')) return projectGraphqlData({ projectItem: false })
|
||||||
if (request.query.includes('addProjectV2ItemById')) {
|
if (request.query.includes('addProjectV2ItemById')) {
|
||||||
return { addProjectV2ItemById: { item: { id: 'new-item-id' } } }
|
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')
|
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.equal(requests.length, 3)
|
||||||
assert.deepEqual(requests[1].variables, { projectId: 'project-id', contentId: 'issue-id' })
|
assert.deepEqual(requests[1].variables, { projectId: 'project-id', contentId: 'issue-id' })
|
||||||
assert.deepEqual(requests[2].variables, {
|
assert.deepEqual(requests[2].variables, {
|
||||||
issueId: 'issue-id',
|
projectId: 'project-id',
|
||||||
fieldId: 'start-date-issue-field-id',
|
itemId: 'new-item-id',
|
||||||
|
fieldId: 'start-date-field-id',
|
||||||
date: '2026-08-28',
|
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 })
|
let response = projectGraphqlData({ startDateField: false })
|
||||||
const requests = mockGraphql(t, () => response)
|
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' })
|
response = projectGraphqlData({ startDateType: 'TEXT' })
|
||||||
await assert.rejects(initializeIssueStartDate(42, '2026-08-28'), /Start date 字段必须为 Date/)
|
await assert.rejects(initializeIssueStartDate(42, '2026-08-28'), /Start Date 字段必须为 Date/)
|
||||||
response = projectGraphqlData({ startDateIsIssueField: false })
|
response = projectGraphqlData({ startDateIsIssueField: true })
|
||||||
await assert.rejects(
|
await assert.rejects(
|
||||||
initializeIssueStartDate(42, '2026-08-28'),
|
initializeIssueStartDate(42, '2026-08-28'),
|
||||||
/Start date 字段必须为 Issue Date 字段/,
|
/Start Date 字段必须为 Project Date 字段/,
|
||||||
)
|
)
|
||||||
response = projectGraphqlData({ startDateIssueField: false })
|
assert.equal(requests.length, 3)
|
||||||
await assert.rejects(
|
|
||||||
initializeIssueStartDate(42, '2026-08-28'),
|
|
||||||
/Start date 字段必须为 Issue Date 字段/,
|
|
||||||
)
|
|
||||||
assert.equal(requests.length, 4)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test('does not treat pull request references as Issue associations', () => {
|
test('does not treat pull request references as Issue associations', () => {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue