🐛 fix(vscode): issue opened 广播按看板 scope 门禁,mine 不再凭空上别人的卡
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
|
||||
import type { ExtensionContext } from 'vscode'
|
||||
import type { KanbanWebviewPanel } from '../panel/KanbanPanel'
|
||||
import type { KanbanScope } from '../panel/kanbanScope'
|
||||
import type { IssueCommentWebhookEvent, IssueWebhookEvent, PrWebhookEvent, WebhookEvent } from './server'
|
||||
import { promises as fsp } from 'node:fs'
|
||||
import { env, Uri, window, workspace } from 'vscode'
|
||||
@@ -24,9 +25,11 @@ import { mergeStateJsonComment, mergeStateJsonCommentGuarded, readStateJsonComme
|
||||
import { getLocalIssueState, mergeLocalIssueState, overlayLocalIssueState } from '../issues/localState'
|
||||
import { logger } from '../logging/logger'
|
||||
import { hasLiveIssueSessionTerminal } from '../panel/handlers/terminals'
|
||||
import { KANBAN_SCOPE_KEY } from '../panel/kanbanScope'
|
||||
import { annotateReviewSessionFileExists } from '../sessions/codexSessions'
|
||||
import { getSettings } from '../settings/store'
|
||||
import { decideEventOwnership } from './eventOwnership'
|
||||
import { decideIssueAppend } from './issueAppend'
|
||||
import { WebhookServer } from './server'
|
||||
|
||||
/**
|
||||
@@ -600,6 +603,30 @@ class WebhookCoordinator {
|
||||
}
|
||||
|
||||
if (this.activePanel) {
|
||||
// 看板 'mine' 范围的批量加载只拉"我创建/指派给我"的工单,而 webhook
|
||||
// 广播到所有机器 → append 前按同一套可见性规则过门禁,否则别人的卡
|
||||
// 会凭空上屏、刷新后又消失。身份/payload 只在 'mine' 且非本机创建时
|
||||
// 才解析,省一次身份请求。
|
||||
const scope = this.ctx.workspaceState.get<KanbanScope>(KANBAN_SCOPE_KEY) ?? 'mine'
|
||||
const pendingCreatedLocally = pending !== undefined
|
||||
let poster: string | undefined
|
||||
let assignees: string[] = []
|
||||
let me: string | undefined
|
||||
if (!pendingCreatedLocally && scope === 'mine') {
|
||||
const raw = event.raw as { issue?: { user?: { login?: string } | null, assignees?: Array<{ login?: string } | null> | null } | null } | null
|
||||
poster = raw?.issue?.user?.login
|
||||
assignees = (raw?.issue?.assignees ?? []).flatMap(a => (a?.login ? [a.login] : []))
|
||||
me = await loginForToken(remote.host, token)
|
||||
}
|
||||
if (!decideIssueAppend({ scope, pendingCreatedLocally, poster, assignees, me })) {
|
||||
logger.add({
|
||||
level: 'info',
|
||||
source: 'webhook',
|
||||
message: `issue #${event.issueNumber} 与我无关,scope=mine 过滤跳过 append`,
|
||||
details: `poster=${poster ?? '<未知>'} assignees=[${assignees.join(',')}] me=${me ?? '<未知>'}`,
|
||||
})
|
||||
return
|
||||
}
|
||||
try {
|
||||
const loaded = await loadSingleIssue({
|
||||
host: remote.host,
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { decideIssueAppend } from './issueAppend'
|
||||
|
||||
describe('decideIssueAppend', () => {
|
||||
it('本机 pending 创建 → 恒 append,范围和身份都不作数', () => {
|
||||
expect(decideIssueAppend({
|
||||
scope: 'mine',
|
||||
pendingCreatedLocally: true,
|
||||
poster: 'chehongwei',
|
||||
assignees: [],
|
||||
me: undefined,
|
||||
})).toBe(true)
|
||||
})
|
||||
|
||||
it('scope=all → append', () => {
|
||||
expect(decideIssueAppend({
|
||||
scope: 'all',
|
||||
pendingCreatedLocally: false,
|
||||
poster: 'chehongwei',
|
||||
assignees: [],
|
||||
me: 'cruldra',
|
||||
})).toBe(true)
|
||||
})
|
||||
|
||||
it('scope=mine 且 poster 是我 → append', () => {
|
||||
expect(decideIssueAppend({
|
||||
scope: 'mine',
|
||||
pendingCreatedLocally: false,
|
||||
poster: 'cruldra',
|
||||
assignees: [],
|
||||
me: 'cruldra',
|
||||
})).toBe(true)
|
||||
})
|
||||
|
||||
it('scope=mine 且 assignee 含我 → append', () => {
|
||||
expect(decideIssueAppend({
|
||||
scope: 'mine',
|
||||
pendingCreatedLocally: false,
|
||||
poster: 'chehongwei',
|
||||
assignees: ['chehongwei', 'cruldra'],
|
||||
me: 'cruldra',
|
||||
})).toBe(true)
|
||||
})
|
||||
|
||||
it('scope=mine 且与我无关 → 不 append', () => {
|
||||
expect(decideIssueAppend({
|
||||
scope: 'mine',
|
||||
pendingCreatedLocally: false,
|
||||
poster: 'chehongwei',
|
||||
assignees: ['chehongwei'],
|
||||
me: 'cruldra',
|
||||
})).toBe(false)
|
||||
})
|
||||
|
||||
it('scope=mine 且身份未知 → 不 append(宁可少显示,刷新兜底)', () => {
|
||||
expect(decideIssueAppend({
|
||||
scope: 'mine',
|
||||
pendingCreatedLocally: false,
|
||||
poster: 'cruldra',
|
||||
assignees: ['cruldra'],
|
||||
me: undefined,
|
||||
})).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,30 @@
|
||||
import type { KanbanScope } from '../panel/kanbanScope'
|
||||
|
||||
/**
|
||||
* issue opened 事件的看板 append 门禁:webhook 广播到所有开着看板的
|
||||
* 机器,而 'mine' 范围的批量加载只拉"我创建/指派给我"的工单。若无条件
|
||||
* append,别人建的单会在 'mine' 看板上凭空多出一张卡(刷新后又消失)。
|
||||
* append 必须与批量加载遵循同一套可见性规则。
|
||||
*/
|
||||
export function decideIssueAppend(opts: {
|
||||
/** 看板当前范围('mine' 只看我的 / 'all' 团队全部)。 */
|
||||
scope: KanbanScope
|
||||
/** 本机面板发起的创建(nonce 匹配到 pending),用户正等这张卡上屏。 */
|
||||
pendingCreatedLocally: boolean
|
||||
/** 工单创建者的 login;payload 缺失时为 undefined。 */
|
||||
poster: string | undefined
|
||||
/** 工单 assignee 的 login 列表,空数组表示未指派。 */
|
||||
assignees: string[]
|
||||
/** 本机 token 对应的 Gitea login;身份解析失败时为 undefined。 */
|
||||
me: string | undefined
|
||||
}): boolean {
|
||||
// ① 本机发起的创建 → 恒显,范围和身份都不作数。
|
||||
if (opts.pendingCreatedLocally)
|
||||
return true
|
||||
// ② 'all' 范围本来就展示团队全部 → 恒显。
|
||||
if (opts.scope === 'all')
|
||||
return true
|
||||
// ③ 'mine' 范围:与批量加载(created_by=我 / assigned_by=我)同规则;
|
||||
// 身份未知时宁可少显示,下次刷新会兜底。
|
||||
return opts.me !== undefined && (opts.poster === opts.me || opts.assignees.includes(opts.me))
|
||||
}
|
||||
Reference in New Issue
Block a user