mirror of
https://github.com/stablyai/orca.git
synced 2026-10-17 00:03:08 +00:00
Clear PR comment selection on AI launch and defer mismatching clears
- Clear sent comments from selection queue when confirming AI resolution. - Keep selection clear requests pending until the matching review context is mounted, preventing them from being ignored on context changes. - Fix calculation of skipped threads when comment resolution is launched.
This commit is contained in:
@@ -49,6 +49,10 @@ import {
|
||||
PRCommentsList,
|
||||
PRTriageStrip
|
||||
} from './checks-panel-content'
|
||||
import {
|
||||
clearPRCommentsListSelection,
|
||||
type PRCommentsListSelectionClearRequest
|
||||
} from './pr-comments-list-selection'
|
||||
import { ENTRY_REFRESH_GRACE_MS, shouldEntryRefresh } from './checks-entry-refresh'
|
||||
import type {
|
||||
GitLabDiscussionResolveResult,
|
||||
@@ -435,6 +439,9 @@ export default function ChecksPanel(): React.JSX.Element {
|
||||
const [comments, setComments] = useState<PRComment[]>([])
|
||||
const [commentsLoading, setCommentsLoading] = useState(false)
|
||||
const commentsRef = useRef<PRComment[]>([])
|
||||
const [commentsSelectionClearRequest, setCommentsSelectionClearRequest] =
|
||||
useState<PRCommentsListSelectionClearRequest | null>(null)
|
||||
const commentsSelectionClearTokenRef = useRef(0)
|
||||
const [emptyRefreshing, setEmptyRefreshing] = useState(false)
|
||||
const [isRefreshing, setIsRefreshing] = useState(false)
|
||||
const refreshInFlightRef = useRef(false)
|
||||
@@ -2789,6 +2796,15 @@ export default function ChecksPanel(): React.JSX.Element {
|
||||
]
|
||||
)
|
||||
|
||||
const clearSentCommentSelection = useCallback((reviewContextKey: string): void => {
|
||||
clearPRCommentsListSelection(reviewContextKey)
|
||||
commentsSelectionClearTokenRef.current += 1
|
||||
setCommentsSelectionClearRequest({
|
||||
contextKey: reviewContextKey,
|
||||
token: commentsSelectionClearTokenRef.current
|
||||
})
|
||||
}, [])
|
||||
|
||||
const refreshCommentsAfterBulkResolve = useCallback(
|
||||
async (provider: ChecksPanelReview['provider']): Promise<void> => {
|
||||
if (provider === 'gitlab') {
|
||||
@@ -2802,9 +2818,14 @@ export default function ChecksPanel(): React.JSX.Element {
|
||||
|
||||
const resolveSelectedThreadsAfterLaunch = useCallback(
|
||||
async (resolution: NonNullable<ChecksAgentComposerState['commentResolution']>) => {
|
||||
clearSentCommentSelection(resolution.reviewContextKey)
|
||||
let resolved = 0
|
||||
let skipped = 0
|
||||
let skipped = Math.max(
|
||||
0,
|
||||
resolution.selectedGroups.length - resolution.selectedThreadIds.length
|
||||
)
|
||||
let failed = 0
|
||||
let attemptedThreadCount = 0
|
||||
if (resolution.selectedThreadIds.length === 0) {
|
||||
toast.success(
|
||||
translate(
|
||||
@@ -2816,9 +2837,10 @@ export default function ChecksPanel(): React.JSX.Element {
|
||||
}
|
||||
for (const threadId of resolution.selectedThreadIds) {
|
||||
if (asyncResultKeyRef.current !== resolution.reviewContextKey) {
|
||||
skipped += resolution.selectedThreadIds.length - resolved - skipped - failed
|
||||
skipped += resolution.selectedThreadIds.length - attemptedThreadCount
|
||||
break
|
||||
}
|
||||
attemptedThreadCount += 1
|
||||
const currentGroup = groupPRComments(commentsRef.current).find(
|
||||
(group) => group.kind === 'thread' && group.threadId === threadId
|
||||
)
|
||||
@@ -2856,7 +2878,7 @@ export default function ChecksPanel(): React.JSX.Element {
|
||||
)
|
||||
)
|
||||
},
|
||||
[handleResolve, refreshCommentsAfterBulkResolve]
|
||||
[clearSentCommentSelection, handleResolve, refreshCommentsAfterBulkResolve]
|
||||
)
|
||||
|
||||
const handleFixChecksWithAI = useCallback(async (): Promise<void> => {
|
||||
@@ -3780,6 +3802,7 @@ export default function ChecksPanel(): React.JSX.Element {
|
||||
commentsDisabled={!canTargetPRComments}
|
||||
commentsDisabledReason={commentsDisabledReason}
|
||||
selectionContextKey={stateRequestKey}
|
||||
selectionClearRequest={commentsSelectionClearRequest}
|
||||
resolveCommentsWithAIDisabled={Boolean(resolveCommentsWithAIDisabledReason)}
|
||||
resolveCommentsWithAIDisabledReason={resolveCommentsWithAIDisabledReason}
|
||||
onAddComment={pr ? handleAddPRComment : undefined}
|
||||
|
||||
@@ -94,7 +94,10 @@ import {
|
||||
RightPanelCommentComposer,
|
||||
type RightPanelCommentSubmitResult
|
||||
} from './right-panel-comment-composer'
|
||||
import { usePRCommentsListSelection } from './pr-comments-list-selection'
|
||||
import {
|
||||
usePRCommentsListSelection,
|
||||
type PRCommentsListSelectionClearRequest
|
||||
} from './pr-comments-list-selection'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useActiveWorktree } from '@/store/selectors'
|
||||
import { useAppStore } from '@/store'
|
||||
@@ -2171,6 +2174,7 @@ export function PRCommentsList({
|
||||
commentsDisabled,
|
||||
commentsDisabledReason,
|
||||
selectionContextKey,
|
||||
selectionClearRequest,
|
||||
resolveCommentsWithAIDisabled,
|
||||
resolveCommentsWithAIDisabledReason,
|
||||
onAddComment,
|
||||
@@ -2186,6 +2190,7 @@ export function PRCommentsList({
|
||||
commentsDisabled?: boolean
|
||||
commentsDisabledReason?: string
|
||||
selectionContextKey?: string
|
||||
selectionClearRequest?: PRCommentsListSelectionClearRequest | null
|
||||
resolveCommentsWithAIDisabled?: boolean
|
||||
resolveCommentsWithAIDisabledReason?: string
|
||||
onAddComment?: (body: string) => Promise<RightPanelCommentSubmitResult>
|
||||
@@ -2212,7 +2217,7 @@ export function PRCommentsList({
|
||||
addGroupToSelection,
|
||||
clearSelection,
|
||||
toggleGroupSelection
|
||||
} = usePRCommentsListSelection(comments, selectionContextKey)
|
||||
} = usePRCommentsListSelection(comments, selectionContextKey, selectionClearRequest)
|
||||
const visibleComments = React.useMemo(
|
||||
() => filterPRCommentsByAudience(comments, commentFilter),
|
||||
[commentFilter, comments]
|
||||
|
||||
@@ -45,7 +45,10 @@ vi.mock('@/components/ui/dropdown-menu', () => ({
|
||||
}))
|
||||
import type { PRComment } from '../../../../shared/types'
|
||||
import type { PRCommentGroup } from '@/lib/pr-comment-groups'
|
||||
import { clearPRCommentsListSelection } from './pr-comments-list-selection'
|
||||
import {
|
||||
clearPRCommentsListSelection,
|
||||
type PRCommentsListSelectionClearRequest
|
||||
} from './pr-comments-list-selection'
|
||||
import { PRCommentsList } from './checks-panel-content'
|
||||
|
||||
let container: HTMLDivElement
|
||||
@@ -79,7 +82,9 @@ function comment(overrides: Partial<PRComment>): PRComment {
|
||||
|
||||
function renderList(props: {
|
||||
comments: PRComment[]
|
||||
contextKey?: string
|
||||
onResolveSelectedCommentsWithAI?: (groups: PRCommentGroup[]) => void
|
||||
clearRequest?: PRCommentsListSelectionClearRequest | null
|
||||
}): void {
|
||||
act(() => {
|
||||
root.render(
|
||||
@@ -87,7 +92,8 @@ function renderList(props: {
|
||||
<PRCommentsList
|
||||
comments={props.comments}
|
||||
commentsLoading={false}
|
||||
selectionContextKey="review:42"
|
||||
selectionContextKey={props.contextKey ?? 'review:42'}
|
||||
selectionClearRequest={props.clearRequest}
|
||||
onResolveSelectedCommentsWithAI={props.onResolveSelectedCommentsWithAI ?? vi.fn()}
|
||||
/>
|
||||
</TooltipProvider>
|
||||
@@ -297,6 +303,58 @@ describe('PRCommentsList comment resolution selection', () => {
|
||||
expect(container.querySelector('button[role="checkbox"]')).toBeNull()
|
||||
})
|
||||
|
||||
it('clears sent standalone bot comments from the queue when the parent confirms launch', () => {
|
||||
const comments = [
|
||||
comment({
|
||||
id: 1,
|
||||
author: 'coderabbitai',
|
||||
body: 'Review Change Stack. No actionable comments were generated.',
|
||||
isBot: true
|
||||
})
|
||||
]
|
||||
renderList({ comments })
|
||||
clickButton('Queue for agent')
|
||||
|
||||
expect(hasButton('Send 1 queued comments to AI')).toBe(true)
|
||||
|
||||
renderList({
|
||||
comments,
|
||||
clearRequest: { contextKey: 'review:42', token: 1 }
|
||||
})
|
||||
|
||||
expect(hasButton('Send 1 queued comments to AI')).toBe(false)
|
||||
expect(container.querySelector('button[role="checkbox"]')).toBeNull()
|
||||
})
|
||||
|
||||
it('keeps a clear request pending until its review context is mounted', () => {
|
||||
const queuedComments = [
|
||||
comment({
|
||||
id: 1,
|
||||
author: 'coderabbitai',
|
||||
body: 'Review Change Stack. No actionable comments were generated.',
|
||||
isBot: true
|
||||
})
|
||||
]
|
||||
renderList({ comments: queuedComments, contextKey: 'review:42' })
|
||||
clickButton('Queue for agent')
|
||||
|
||||
expect(hasButton('Send 1 queued comments to AI')).toBe(true)
|
||||
|
||||
renderList({
|
||||
comments: [comment({ id: 2, body: 'Other review comment.' })],
|
||||
contextKey: 'review:99',
|
||||
clearRequest: { contextKey: 'review:42', token: 1 }
|
||||
})
|
||||
renderList({
|
||||
comments: queuedComments,
|
||||
contextKey: 'review:42',
|
||||
clearRequest: { contextKey: 'review:42', token: 1 }
|
||||
})
|
||||
|
||||
expect(hasButton('Send 1 queued comments to AI')).toBe(false)
|
||||
expect(container.querySelector('button[role="checkbox"]')).toBeNull()
|
||||
})
|
||||
|
||||
it('exits selection mode when refresh leaves no eligible loaded threads', () => {
|
||||
renderList({
|
||||
comments: [comment({ id: 1, threadId: 'thread-1', path: 'src/a.ts', isResolved: false })]
|
||||
|
||||
@@ -65,7 +65,7 @@ export function usePRCommentsListSelection(
|
||||
selectionContextKey: string | undefined,
|
||||
clearRequest?: PRCommentsListSelectionClearRequest | null
|
||||
): PRCommentsListSelection {
|
||||
const lastClearRequestTokenRef = useRef<number | null>(clearRequest?.token ?? null)
|
||||
const lastClearRequestTokenRef = useRef<number | null>(null)
|
||||
const [selectionState, setSelectionState] = useState<PRCommentsListSelectionState>(() =>
|
||||
createSelectionState(selectionContextKey)
|
||||
)
|
||||
@@ -77,13 +77,14 @@ export function usePRCommentsListSelection(
|
||||
}, [selectionContextKey])
|
||||
|
||||
useEffect(() => {
|
||||
if (!clearRequest || clearRequest.token === lastClearRequestTokenRef.current) {
|
||||
if (
|
||||
!clearRequest ||
|
||||
clearRequest.contextKey !== selectionContextKey ||
|
||||
clearRequest.token === lastClearRequestTokenRef.current
|
||||
) {
|
||||
return
|
||||
}
|
||||
lastClearRequestTokenRef.current = clearRequest.token
|
||||
if (clearRequest.contextKey !== selectionContextKey) {
|
||||
return
|
||||
}
|
||||
const next = {
|
||||
contextKey: selectionContextKey,
|
||||
isSelectingForAI: false,
|
||||
|
||||
Reference in New Issue
Block a user