🐛 fix(vscode): 完成列缺 PR 合并时间时按工单关闭时间排序
没走 PR 流程、直接关掉的工单没有 merged_at,原先一律排在所有已合并 工单之后,在上百张卡的完成列里等于永远看不见。loader 把 Gitea issue 的 closed_at 带进 Issue,完成列排序缺 prMergedAt 时回退到它,让两类 工单落在同一条时间线上。
This commit is contained in:
1 parent
0b85b9868b
commit
73f2c3ec66
6 files changed
+73
-4
No files matched your search
@@ -29,6 +29,8 @@ export interface GiteaIssue {
|
||||
comments: number
|
||||
created_at: string
|
||||
updated_at: string
|
||||
/** 工单关闭时间(ISO 8601);未关闭为 null。完成列缺 PR 合并时间时用它排序。 */
|
||||
closed_at?: string | null
|
||||
body: string
|
||||
html_url: string
|
||||
user: { login: string, avatar_url?: string } | null
|
||||
|
||||
@@ -435,6 +435,7 @@ async function buildIssue(opts: {
|
||||
...(pr ? { pr } : {}),
|
||||
...(prMerged !== undefined ? { prMerged } : {}),
|
||||
...(mergedAt ? { prMergedAt: mergedAt } : {}),
|
||||
...(issue.closed_at ? { closedAt: issue.closed_at } : {}),
|
||||
...(branch ? { branch } : {}),
|
||||
...(worktreePath ? { worktreePath } : {}),
|
||||
...(worktreeExists !== undefined ? { worktreeExists } : {}),
|
||||
|
||||
@@ -76,6 +76,8 @@ export interface Issue {
|
||||
prMerged?: boolean
|
||||
/** PR 合并时间(ISO 8601,来自 PR API 的 merged_at)。完成列按它降序排序,未合并为 undefined。合并时持久化进 state JSON;完成工单加载时不再实时拉 PR,直接读持久化值(缺失则按工单号回退排序)。 */
|
||||
prMergedAt?: string
|
||||
/** 工单关闭时间(ISO 8601,来自 Gitea issue 的 closed_at)。完成列缺 prMergedAt 时按它排序,直接关掉、没走 PR 的工单才不会沉底。 */
|
||||
closedAt?: string
|
||||
/** Branch name created for implementation, e.g. `feature/<hash>`. */
|
||||
branch?: string
|
||||
/** Workspace-relative path to the implementation worktree. */
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import type { Issue, IssueColumn } from '../types'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { compareIssuesInColumn } from './issueSort'
|
||||
|
||||
function issue(
|
||||
number: number,
|
||||
column: IssueColumn,
|
||||
extra: Partial<Issue> = {},
|
||||
): Issue {
|
||||
return {
|
||||
id: `repo#${number}`,
|
||||
number,
|
||||
title: `issue ${number}`,
|
||||
column,
|
||||
htmlUrl: '',
|
||||
...extra,
|
||||
}
|
||||
}
|
||||
|
||||
function sorted(col: IssueColumn, issues: Issue[]): number[] {
|
||||
return [...issues].sort((a, b) => compareIssuesInColumn(col, a, b)).map(i => i.number)
|
||||
}
|
||||
|
||||
describe('compareIssuesInColumn', () => {
|
||||
it('非完成列按工单号降序', () => {
|
||||
const list = [issue(10, 'todo'), issue(42, 'todo'), issue(7, 'todo')]
|
||||
expect(sorted('todo', list)).toEqual([42, 10, 7])
|
||||
})
|
||||
|
||||
it('完成列按 PR 合并时间降序', () => {
|
||||
const list = [
|
||||
issue(1, 'done', { prMergedAt: '2026-09-01T00:00:00Z' }),
|
||||
issue(2, 'done', { prMergedAt: '2026-09-20T00:00:00Z' }),
|
||||
issue(3, 'done', { prMergedAt: '2026-09-10T00:00:00Z' }),
|
||||
]
|
||||
expect(sorted('done', list)).toEqual([2, 3, 1])
|
||||
})
|
||||
|
||||
it('没有 PR 的完成工单用关闭时间参与同一条时间线,不沉底', () => {
|
||||
const list = [
|
||||
issue(100, 'done', { prMergedAt: '2026-09-01T00:00:00Z' }),
|
||||
issue(427, 'done', { closedAt: '2026-09-20T12:00:00Z' }),
|
||||
issue(300, 'done', { prMergedAt: '2026-09-10T00:00:00Z' }),
|
||||
]
|
||||
expect(sorted('done', list)).toEqual([427, 300, 100])
|
||||
})
|
||||
|
||||
it('两个时间都缺的排在最后,按工单号降序', () => {
|
||||
const list = [
|
||||
issue(5, 'done'),
|
||||
issue(9, 'done'),
|
||||
issue(1, 'done', { closedAt: '2026-01-01T00:00:00Z' }),
|
||||
]
|
||||
expect(sorted('done', list)).toEqual([1, 9, 5])
|
||||
})
|
||||
})
|
||||
@@ -3,13 +3,19 @@ import type { Issue, IssueColumn } from '../types'
|
||||
/**
|
||||
* 看板每一列的卡片排序。
|
||||
*
|
||||
* `done` 列按 PR 合并时间(merged_at)降序,最近合并的在最前;缺合并时间的
|
||||
* 工单排在有时间的之后,按工单号降序兜底。其余列一律按工单号降序。
|
||||
* `done` 列按"完成时间"降序,最近完成的在最前。完成时间取 PR 合并时间
|
||||
* (merged_at),没有 PR 的工单退回工单关闭时间(closed_at)——不这样兜底,
|
||||
* 直接关掉、没走 PR 流程的工单会永远沉在列底看不见。两者都没有的排最后,
|
||||
* 按工单号降序。其余列一律按工单号降序。
|
||||
*/
|
||||
function doneAt(issue: Issue): string | undefined {
|
||||
return issue.prMergedAt ?? issue.closedAt
|
||||
}
|
||||
|
||||
export function compareIssuesInColumn(col: IssueColumn, a: Issue, b: Issue): number {
|
||||
if (col === 'done') {
|
||||
const am = a.prMergedAt
|
||||
const bm = b.prMergedAt
|
||||
const am = doneAt(a)
|
||||
const bm = doneAt(b)
|
||||
if (am && bm && am !== bm)
|
||||
return bm.localeCompare(am)
|
||||
if (am && !bm)
|
||||
|
||||
@@ -53,6 +53,8 @@ export interface Issue {
|
||||
prMerged?: boolean
|
||||
/** PR 合并时间(ISO 8601,来自 merged_at)。完成列按它降序排序,未合并/查询失败为 undefined。不持久化进 state JSON。 */
|
||||
prMergedAt?: string
|
||||
/** 工单关闭时间(ISO 8601)。完成列缺 prMergedAt 时按它排序。 */
|
||||
closedAt?: string
|
||||
/** Branch name created for implementation, e.g. `feature/<hash>`. */
|
||||
branch?: string
|
||||
/** Workspace-relative path to the implementation worktree. */
|
||||
|
||||
Reference in new issue
Block a user