fix(gitlab): label hosted reviews per provider in the space-manager panel (#7655)

The workspace space-manager rendered every hosted review as "PR #N", so a
GitLab merge request showed as "PR #5" instead of "MR !5". The cached review
already carries its provider (HostedReviewInfo.provider) — the panel's local
input type just narrowed it away. Thread provider through and format via the
same localizedHostedReviewCopy / '!' vs '#' helpers the rest of the app uses.

Extract the label formatting into hosted-review-label.ts (a pure module, away
from the panel's heavy React/store imports) so it can be unit-tested directly,
and add hosted-review-label.test.ts covering github / gitlab / azure-devops /
gitea and the status suffix.

linkedPR stays "PR #N" — it is the GitHub-specific workspace field, distinct
from linkedGitLabMR.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Neil <neil@stably.ai>
This commit is contained in:
Mark Xian
2026-10-23 19:20:05 -07:00
committed by GitHub
co-authored by Claude Opus 4.8 Neil Neil
parent 6fd0682ac1
commit 831c394797
4 changed files with 65 additions and 8 deletions
@@ -0,0 +1,39 @@
import { describe, expect, it } from 'vitest'
import { formatHostedReviewLabel } from './hosted-review-label'
describe('formatHostedReviewLabel', () => {
it('labels a GitLab review as "MR !N" — not "PR #N"', () => {
expect(
formatHostedReviewLabel({ provider: 'gitlab', number: 5, state: 'open', status: 'none' })
).toBe('MR !5 Open')
})
it.each([
['github' as const, 'PR #7 Open'],
['azure-devops' as const, 'PR #7 Open'],
['gitea' as const, 'PR #7 Open'],
['bitbucket' as const, 'PR #7 Open']
])('uses the "PR #" form and correct copy for %s', (provider, expected) => {
expect(formatHostedReviewLabel({ provider, number: 7, state: 'open', status: 'none' })).toBe(
expected
)
})
it('keeps the GitHub label for older cached reviews without a provider', () => {
expect(formatHostedReviewLabel({ number: 42, state: 'open', status: 'none' })).toBe(
'PR #42 Open'
)
})
it('appends a non-"none" status suffix', () => {
expect(
formatHostedReviewLabel({ provider: 'gitlab', number: 5, state: 'open', status: 'draft' })
).toBe('MR !5 Open, draft')
})
it('omits the suffix when status is "none" or empty', () => {
expect(
formatHostedReviewLabel({ provider: 'github', number: 5, state: 'closed', status: '' })
).toBe('PR #5 Closed')
})
})
@@ -0,0 +1,23 @@
import type { HostedReviewProvider } from '../../../../shared/hosted-review'
import {
localizedHostedReviewCopy,
resolveSupportedHostedReviewCopyProvider
} from '@/i18n/hosted-review-localized-copy'
function formatReviewState(state: string): string {
return state.charAt(0).toUpperCase() + state.slice(1)
}
export function formatHostedReviewLabel(review: {
provider?: HostedReviewProvider
number: number
state: string
status: string
}): string {
const { shortLabel } = localizedHostedReviewCopy(
resolveSupportedHostedReviewCopyProvider(review.provider)
)
const symbol = review.provider === 'gitlab' ? '!' : '#'
const statusSuffix = review.status && review.status !== 'none' ? `, ${review.status}` : ''
return `${shortLabel} ${symbol}${review.number} ${formatReviewState(review.state)}${statusSuffix}`
}
@@ -16,6 +16,7 @@ import { issueCacheKey as getIssueCacheKey } from '../../store/github/cache-iden
import { countWorkspaceSpaceActiveAgents } from './workspace-space-presentation'
import { getWorkspaceSpaceWorktreeIdentity } from './workspace-space-delete-selection'
import { getWorkspaceSpaceBranchLabel } from './workspace-space-format'
import { formatHostedReviewLabel } from './hosted-review-label'
export type WorkspaceDecisionDetails = {
isActive: boolean
@@ -78,10 +79,6 @@ export function pluralize(count: number, singular: string, plural = `${singular}
return `${count} ${count === 1 ? singular : plural}`
}
function formatReviewState(state: string): string {
return state.charAt(0).toUpperCase() + state.slice(1)
}
function countLiveTerminals(
tabs: readonly TerminalTab[],
ptyIdsByTabId: Record<string, string[]>
@@ -156,9 +153,7 @@ export function getWorkspaceDecisionDetails(
const linkedPR = workspaceRecord?.linkedPR ?? null
const reviewLabel =
hostedReview !== undefined && hostedReview !== null
? `PR #${hostedReview.number} ${formatReviewState(hostedReview.state)}${
hostedReview.status && hostedReview.status !== 'none' ? `, ${hostedReview.status}` : ''
}`
? formatHostedReviewLabel(hostedReview)
: linkedPR
? `PR #${linkedPR}`
: null
@@ -564,7 +564,7 @@ describe('workspace space presentation helpers', () => {
)
expect(explicit.reviewLabel).toBe('PR #12 Open, success')
expect(gitLab.reviewLabel).toBe('PR #12 Open, success')
expect(gitLab.reviewLabel).toBe('MR !12 Open, success')
})
it('counts migration-unsupported agent entries by worktree id', () => {