mirror of
https://github.com/stablyai/orca.git
synced 2026-09-25 00:02:35 +00:00
Show GitHub PR reviewer names in task rows (#2309)
This commit is contained in:
@@ -114,7 +114,16 @@ describe('listWorkItems', () => {
|
||||
author: { login: 'octocat' },
|
||||
isDraft: false,
|
||||
headRefName: 'feature/add-feature',
|
||||
baseRefName: 'main'
|
||||
baseRefName: 'main',
|
||||
reviewRequests: [
|
||||
{
|
||||
requestedReviewer: {
|
||||
login: 'AmethystLiang',
|
||||
name: 'Amethyst Liang',
|
||||
avatarUrl: 'https://avatars.githubusercontent.com/u/1?v=4'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
])
|
||||
})
|
||||
@@ -147,7 +156,7 @@ describe('listWorkItems', () => {
|
||||
'--limit',
|
||||
'10',
|
||||
'--json',
|
||||
'number,title,state,url,labels,updatedAt,author,isDraft,headRefName,baseRefName,headRepositoryOwner',
|
||||
'number,title,state,url,labels,updatedAt,author,isDraft,headRefName,baseRefName,headRepositoryOwner,reviewRequests',
|
||||
'--repo',
|
||||
'acme/widgets',
|
||||
'--assignee',
|
||||
@@ -157,7 +166,7 @@ describe('listWorkItems', () => {
|
||||
)
|
||||
const prListFields = ghExecFileAsyncMock.mock.calls[1][0].join(',')
|
||||
expect(prListFields).not.toContain('statusCheckRollup')
|
||||
expect(prListFields).not.toContain('reviewRequests')
|
||||
expect(prListFields).toContain('reviewRequests')
|
||||
expect(prListFields).not.toContain('mergeStateStatus')
|
||||
expect(items).toEqual([
|
||||
{
|
||||
@@ -182,7 +191,14 @@ describe('listWorkItems', () => {
|
||||
updatedAt: '2026-03-28T00:00:00Z',
|
||||
author: 'octocat',
|
||||
branchName: 'feature/add-feature',
|
||||
baseRefName: 'main'
|
||||
baseRefName: 'main',
|
||||
reviewRequests: [
|
||||
{
|
||||
login: 'AmethystLiang',
|
||||
name: 'Amethyst Liang',
|
||||
avatarUrl: 'https://avatars.githubusercontent.com/u/1?v=4'
|
||||
}
|
||||
]
|
||||
}
|
||||
])
|
||||
})
|
||||
@@ -215,7 +231,7 @@ describe('listWorkItems', () => {
|
||||
'--limit',
|
||||
'10',
|
||||
'--json',
|
||||
'number,title,state,url,labels,updatedAt,author,isDraft,headRefName,baseRefName,headRepositoryOwner',
|
||||
'number,title,state,url,labels,updatedAt,author,isDraft,headRefName,baseRefName,headRepositoryOwner,reviewRequests',
|
||||
'--repo',
|
||||
'acme/widgets',
|
||||
'--state',
|
||||
@@ -315,7 +331,7 @@ describe('listWorkItems', () => {
|
||||
'--limit',
|
||||
'10',
|
||||
'--json',
|
||||
'number,title,state,url,labels,updatedAt,author,isDraft,headRefName,baseRefName,headRepositoryOwner',
|
||||
'number,title,state,url,labels,updatedAt,author,isDraft,headRefName,baseRefName,headRepositoryOwner,reviewRequests',
|
||||
'--repo',
|
||||
'acme/widgets',
|
||||
'--state',
|
||||
@@ -354,26 +370,24 @@ describe('listWorkItems', () => {
|
||||
it('marks fork PRs as cross-repository when REST payload only includes head.label', async () => {
|
||||
getIssueOwnerRepoMock.mockResolvedValueOnce({ owner: 'stablyai', repo: 'orca' })
|
||||
getOwnerRepoMock.mockResolvedValueOnce({ owner: 'stablyai', repo: 'orca' })
|
||||
ghExecFileAsyncMock
|
||||
.mockResolvedValueOnce({ stdout: '[]' })
|
||||
.mockResolvedValueOnce({
|
||||
stdout: JSON.stringify([
|
||||
{
|
||||
number: 1849,
|
||||
title: 'Fork PR with missing head repo',
|
||||
state: 'open',
|
||||
html_url: 'https://github.com/stablyai/orca/pull/1849',
|
||||
updated_at: '2026-04-01T00:00:00Z',
|
||||
user: { login: 'contributor' },
|
||||
head: {
|
||||
ref: 'feat/onboarding-model-choice-782',
|
||||
repo: null,
|
||||
label: 'contributor:feat/onboarding-model-choice-782'
|
||||
},
|
||||
base: { ref: 'main' }
|
||||
}
|
||||
])
|
||||
})
|
||||
ghExecFileAsyncMock.mockResolvedValueOnce({ stdout: '[]' }).mockResolvedValueOnce({
|
||||
stdout: JSON.stringify([
|
||||
{
|
||||
number: 1849,
|
||||
title: 'Fork PR with missing head repo',
|
||||
state: 'open',
|
||||
html_url: 'https://github.com/stablyai/orca/pull/1849',
|
||||
updated_at: '2026-04-01T00:00:00Z',
|
||||
user: { login: 'contributor' },
|
||||
head: {
|
||||
ref: 'feat/onboarding-model-choice-782',
|
||||
repo: null,
|
||||
label: 'contributor:feat/onboarding-model-choice-782'
|
||||
},
|
||||
base: { ref: 'main' }
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
const { items } = await listWorkItems('/repo-root', 10)
|
||||
expect(items).toEqual([
|
||||
|
||||
@@ -84,6 +84,7 @@ vi.mock('./rate-limit', () => ({
|
||||
import {
|
||||
getPRComments,
|
||||
getPRForBranch,
|
||||
getWorkItem,
|
||||
getPullRequestPushTarget,
|
||||
mergePR,
|
||||
resolveReviewThread,
|
||||
@@ -824,17 +825,46 @@ describe('getPRForBranch', () => {
|
||||
remoteName: 'origin',
|
||||
branchName: 'feature/test'
|
||||
})
|
||||
expect(ghExecFileAsyncMock).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
['api', 'repos/fork/orca/pulls/1849'],
|
||||
{ cwd: '/repo-root' }
|
||||
)
|
||||
expect(ghExecFileAsyncMock).toHaveBeenNthCalledWith(1, ['api', 'repos/fork/orca/pulls/1849'], {
|
||||
cwd: '/repo-root'
|
||||
})
|
||||
expect(ghExecFileAsyncMock).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
['api', 'repos/stablyai/orca/pulls/1849'],
|
||||
{ cwd: '/repo-root' }
|
||||
)
|
||||
})
|
||||
|
||||
it('normalizes reviewer avatars from REST pull request payloads', async () => {
|
||||
getOwnerRepoMock.mockResolvedValueOnce({ owner: 'acme', repo: 'widgets' })
|
||||
ghExecFileAsyncMock.mockResolvedValueOnce({
|
||||
stdout: JSON.stringify({
|
||||
number: 42,
|
||||
title: 'Review me',
|
||||
state: 'open',
|
||||
html_url: 'https://github.com/acme/widgets/pull/42',
|
||||
labels: [],
|
||||
updated_at: '2026-03-28T00:00:00Z',
|
||||
user: { login: 'author' },
|
||||
draft: false,
|
||||
requested_reviewers: [
|
||||
{
|
||||
login: 'AmethystLiang',
|
||||
avatar_url: 'https://avatars.githubusercontent.com/u/1?v=4'
|
||||
}
|
||||
]
|
||||
})
|
||||
})
|
||||
|
||||
await expect(getWorkItem('/repo-root', 42, 'pr')).resolves.toMatchObject({
|
||||
reviewRequests: [
|
||||
{
|
||||
login: 'AmethystLiang',
|
||||
avatarUrl: 'https://avatars.githubusercontent.com/u/1?v=4'
|
||||
}
|
||||
]
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('GitHub GraphQL rate-limit guard', () => {
|
||||
|
||||
@@ -249,11 +249,12 @@ export async function getAuthenticatedViewer(): Promise<GitHubViewer | null> {
|
||||
type MainWorkItem = Omit<GitHubWorkItem, 'repoId'>
|
||||
|
||||
const WORK_ITEM_PR_LIST_JSON_FIELDS =
|
||||
'number,title,state,url,labels,updatedAt,author,isDraft,headRefName,baseRefName,headRepositoryOwner'
|
||||
'number,title,state,url,labels,updatedAt,author,isDraft,headRefName,baseRefName,headRepositoryOwner,reviewRequests'
|
||||
|
||||
// Why: these fields are intentionally excluded from `gh pr list` because
|
||||
// statusCheckRollup/review/merge metadata fan out into expensive GraphQL work
|
||||
// across every row. Fetch them only for single-PR detail surfaces.
|
||||
// statusCheckRollup/review decision/merge metadata fan out into expensive
|
||||
// GraphQL work across every row. Requested reviewers are kept in the list
|
||||
// payload because the Tasks table renders that column on first paint.
|
||||
const WORK_ITEM_PR_DETAIL_JSON_FIELDS =
|
||||
'number,title,state,url,labels,updatedAt,author,isDraft,headRefName,baseRefName,headRepositoryOwner,additions,deletions,changedFiles,reviewDecision,reviewRequests,latestReviews,assignees,statusCheckRollup,mergeable,mergeStateStatus,maintainerCanModify'
|
||||
|
||||
@@ -342,7 +343,12 @@ function userFromUnknown(
|
||||
return {
|
||||
login,
|
||||
name: typeof raw.name === 'string' ? raw.name : null,
|
||||
avatarUrl: typeof raw.avatarUrl === 'string' ? raw.avatarUrl : ''
|
||||
avatarUrl:
|
||||
typeof raw.avatarUrl === 'string'
|
||||
? raw.avatarUrl
|
||||
: typeof raw.avatar_url === 'string'
|
||||
? raw.avatar_url
|
||||
: ''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2504,7 +2510,7 @@ export async function requestPRReviewers(
|
||||
): Promise<{ ok: true } | { ok: false; error: string }> {
|
||||
const logins = reviewers.map((reviewer) => reviewer.trim()).filter(Boolean)
|
||||
if (logins.length === 0) {
|
||||
return { ok: false, error: 'Enter at least one reviewer login' }
|
||||
return { ok: false, error: 'Enter at least one reviewer' }
|
||||
}
|
||||
const ghOptions = ghRepoExecOptions(githubRepoContext(repoPath, connectionId))
|
||||
const ownerRepo = await getOwnerRepo(repoPath, connectionId)
|
||||
|
||||
Reference in New Issue
Block a user