mirror of
https://github.com/stablyai/orca.git
synced 2026-11-15 10:27:33 +00:00
fix(cloud): unreachable GitHub repository on a remote-server project says so with More access (UNREACHABLE-REPO-NO-ROUTE) (#730)
* fix(cloud): Orca Cloud on a remote-server project whose GitHub repository it can't reach says so with More access, not the folder wording (UNREACHABLE-REPO-NO-ROUTE) * fix(cloud): UNREACHABLE-REPO-NO-ROUTE design-squad: 'Give Orca access' link, muted (not red) precondition line, fixture * test(cloud): OrcaCloudNeedsRepoSubmit fixtures press the footer's Create * fix(cloud): UNREACHABLE-REPO-NO-ROUTE the Project field's ring is amber while Orca Cloud waits for a GitHub repository (design-squad); aria-invalid kept * fix(cloud): UNREACHABLE-REPO-NO-ROUTE ring classes stay literal (localization audit), drop the unreferenced remoteServerDetail key
This commit is contained in:
+1
@@ -25,6 +25,7 @@ function cloudOnly(
|
||||
picked: null,
|
||||
folderProject: false,
|
||||
needsGitHubPick: false,
|
||||
remoteServerProject: false,
|
||||
githubStep: null,
|
||||
pick: () => {},
|
||||
clear: () => {},
|
||||
|
||||
+1
@@ -99,6 +99,7 @@ const handoffCloudStart = (githubHandoff: 'waiting' | 'timed-out'): CloudStartVi
|
||||
picked: null,
|
||||
folderProject: false,
|
||||
needsGitHubPick: false,
|
||||
remoteServerProject: false,
|
||||
githubStep: null,
|
||||
pick: () => {},
|
||||
clear: () => {},
|
||||
|
||||
@@ -236,6 +236,8 @@ export function NewWorkspaceComposerProjectSection({
|
||||
}
|
||||
triggerClassName="h-9 w-full border-input text-sm focus-visible:border-focus-ring focus-visible:ring-1 focus-visible:ring-focus-ring"
|
||||
invalid={Boolean(projectError) || Boolean(needsRepo)}
|
||||
// Waiting for a GitHub repository is a fixable precondition: amber, not red.
|
||||
invalidTone={!projectError && needsRepo ? 'warning' : 'error'}
|
||||
describedBy={
|
||||
!projectError && needsRepo ? ORCA_CLOUD_NEEDS_REPO_ID : projectDescriptionId
|
||||
}
|
||||
@@ -255,6 +257,8 @@ export function NewWorkspaceComposerProjectSection({
|
||||
<OrcaCloudNeedsRepoLine
|
||||
hasRepos={needsRepo.hasRepos}
|
||||
onAddRepository={onAddProject}
|
||||
unreachable={needsRepo.unreachable === true}
|
||||
onMoreAccess={cloudOnly?.moreAccess}
|
||||
/>
|
||||
) : projectOptions.length === 0 ? (
|
||||
<p id={projectDescriptionId} className="text-[11px] text-muted-foreground">
|
||||
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
import { ProjectOptionMark, ProjectOptionRow } from './ProjectComboboxRow'
|
||||
import { useRecentProjectIds } from './use-recent-project-ids'
|
||||
import { isWithinComboboxRoot, useTypeAheadCombobox } from './use-type-ahead-combobox'
|
||||
import type { ProjectComboboxProps } from './project-combobox-props'
|
||||
import { projectFieldInvalidClass, type ProjectComboboxProps } from './project-combobox-props'
|
||||
import { COMBOBOX_FIELD_SHELL, COMBOBOX_POPOVER_SURFACE } from './type-ahead-combobox-styles'
|
||||
import {
|
||||
cloudRepoKey,
|
||||
@@ -48,6 +48,7 @@ export default function ProjectCombobox({
|
||||
placeholder = 'Choose project',
|
||||
triggerClassName,
|
||||
invalid = false,
|
||||
invalidTone = 'error',
|
||||
describedBy,
|
||||
cloudRepos = null,
|
||||
cloudRepoValue = null,
|
||||
@@ -218,9 +219,8 @@ export default function ProjectCombobox({
|
||||
}}
|
||||
className={cn(
|
||||
COMBOBOX_FIELD_SHELL,
|
||||
// Invalid stays red while focused, so the field and its error read as one.
|
||||
invalid &&
|
||||
'border-destructive ring-destructive/20 focus-within:border-destructive focus-within:ring-destructive/40 dark:ring-destructive/40',
|
||||
// Invalid stays marked while focused, so the field and its line read as one.
|
||||
invalid && projectFieldInvalidClass(invalidTone),
|
||||
triggerClassName
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -13,6 +13,11 @@ export type ProjectComboboxProps = {
|
||||
placeholder?: string
|
||||
triggerClassName?: string
|
||||
invalid?: boolean
|
||||
/**
|
||||
* `warning`: a fixable precondition (Orca Cloud waiting for a GitHub repository), ringed amber
|
||||
* rather than red; still aria-invalid for screen readers. Defaults to `error`.
|
||||
*/
|
||||
invalidTone?: 'error' | 'warning'
|
||||
describedBy?: string
|
||||
/** D4: GitHub repositories with no clone here, listed while Orca Cloud is the Run on target. */
|
||||
cloudRepos?: readonly OrcaCloudGitHubRepository[] | null
|
||||
@@ -32,3 +37,10 @@ export type ProjectComboboxProps = {
|
||||
*/
|
||||
focusSignal?: number
|
||||
}
|
||||
|
||||
/** The field's invalid ring: red for an error, amber for a fixable precondition (glossary). */
|
||||
export function projectFieldInvalidClass(tone: 'error' | 'warning'): string {
|
||||
return tone === 'warning'
|
||||
? 'border-status-warning ring-status-warning/20 focus-within:border-status-warning focus-within:ring-status-warning/40 dark:ring-status-warning/40'
|
||||
: 'border-destructive ring-destructive/20 focus-within:border-destructive focus-within:ring-destructive/40 dark:ring-destructive/40'
|
||||
}
|
||||
|
||||
@@ -10,12 +10,44 @@ import { ORCA_CLOUD_NEEDS_REPO_ID } from './cloud-start-view'
|
||||
*/
|
||||
export function OrcaCloudNeedsRepoLine({
|
||||
hasRepos,
|
||||
onAddRepository
|
||||
onAddRepository,
|
||||
unreachable = false,
|
||||
onMoreAccess
|
||||
}: {
|
||||
hasRepos: boolean
|
||||
onAddRepository: () => void
|
||||
/** UNREACHABLE-REPO-NO-ROUTE: the project's own GitHub repository is out of Orca Cloud's reach. */
|
||||
unreachable?: boolean
|
||||
/** Opens GitHub to give Orca access to more repositories ("Give Orca access"). */
|
||||
onMoreAccess?: () => void
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
if (unreachable) {
|
||||
return (
|
||||
<div
|
||||
id={ORCA_CLOUD_NEEDS_REPO_ID}
|
||||
role="alert"
|
||||
className="flex flex-col items-start text-[11px]"
|
||||
data-orca-cloud-needs-repo="unreachable"
|
||||
>
|
||||
<p className="text-muted-foreground">
|
||||
{t(
|
||||
'components.newWorkspace.cloudOnly.remoteServerUnreachable',
|
||||
'Orca Cloud starts from a GitHub repository and can’t reach this project’s. Give Orca access to it, or pick another repository.'
|
||||
)}
|
||||
</p>
|
||||
{onMoreAccess ? (
|
||||
<div className="-mx-1">
|
||||
<Button type="button" variant="link" size="statusAction" onClick={onMoreAccess}>
|
||||
<span className="underline underline-offset-4">
|
||||
{t('worktreeCreation.cloneAccess.giveAccess', 'Give Orca access')}
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div
|
||||
id={ORCA_CLOUD_NEEDS_REPO_ID}
|
||||
@@ -23,7 +55,7 @@ export function OrcaCloudNeedsRepoLine({
|
||||
className="flex flex-col items-start text-[11px]"
|
||||
data-orca-cloud-needs-repo
|
||||
>
|
||||
<p className="text-destructive">
|
||||
<p className="text-muted-foreground">
|
||||
{t(
|
||||
'components.newWorkspace.cloudOnly.needsRepository',
|
||||
'Orca Cloud needs a GitHub repository.'
|
||||
|
||||
+37
-4
@@ -37,17 +37,40 @@ const orcaCloud: EphemeralVmRecipeOption = {
|
||||
create: 'orca-cloud create'
|
||||
}
|
||||
const NEEDS_REPO = { hasRepos: true }
|
||||
// UNREACHABLE-REPO-NO-ROUTE: Hello-World cloned only on a paired server, out of Orca Cloud's reach.
|
||||
const UNREACHABLE = { hasRepos: true, unreachable: true }
|
||||
|
||||
function Composer(): React.JSX.Element {
|
||||
function Composer({ unreachable = false }: { unreachable?: boolean }): React.JSX.Element {
|
||||
const [project, setProject] = useState('folder')
|
||||
const [created, setCreated] = useState(false)
|
||||
const needsRepo = project === 'folder' ? NEEDS_REPO : null
|
||||
const needsRepo = project === 'folder' ? (unreachable ? UNREACHABLE : NEEDS_REPO) : null
|
||||
const guard = useNeedsRepoSubmitGuard(needsRepo, () => setCreated(true))
|
||||
const cloudStart = {
|
||||
onSelectOrcaCloud: () => {},
|
||||
onUserPick: () => {},
|
||||
folderProject: project === 'folder',
|
||||
needsGitHubRepo: guard.shown
|
||||
needsGitHubRepo: guard.shown,
|
||||
...(unreachable
|
||||
? {
|
||||
cloudOnly: {
|
||||
repos: [],
|
||||
showMore: null,
|
||||
retryRepos: null,
|
||||
retryingRepos: false,
|
||||
picked: null,
|
||||
folderProject: false,
|
||||
needsGitHubPick: true,
|
||||
remoteServerProject: true,
|
||||
githubStep: null,
|
||||
pick: () => {},
|
||||
clear: () => {},
|
||||
moreAccess: () => {},
|
||||
githubHandoff: 'idle' as const,
|
||||
checkGitHubAgain: () => {},
|
||||
start: async () => false
|
||||
}
|
||||
}
|
||||
: {})
|
||||
}
|
||||
return (
|
||||
<div className="w-[520px] space-y-4 rounded-xl border border-border bg-background p-4">
|
||||
@@ -99,7 +122,8 @@ function Composer(): React.JSX.Element {
|
||||
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
|
||||
const clickCreate = (frame: HTMLElement): void =>
|
||||
[...frame.querySelectorAll<HTMLButtonElement>('button')]
|
||||
.find((button) => button.textContent?.includes('Create workspace'))
|
||||
// The footer's primary reads "Create" (its shortcut chip follows).
|
||||
.find((button) => /^Create\b/.test(button.textContent?.trim() ?? ''))
|
||||
?.click()
|
||||
|
||||
const base = {
|
||||
@@ -131,3 +155,12 @@ export const fixed = defineFixture({
|
||||
await wait(200)
|
||||
}
|
||||
})
|
||||
/** A remote-server project whose GitHub repository Orca Cloud can't reach, after Create. */
|
||||
export const unreachableAfterSubmit = defineFixture({
|
||||
...base,
|
||||
render: () => <Composer unreachable />,
|
||||
interact: async (frame) => {
|
||||
clickCreate(frame)
|
||||
await wait(200)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -16,7 +16,7 @@ export type CloudStartView = {
|
||||
* Project. `hasRepos`: any to choose from. `attempt` counts those stopped Creates (focus moves to
|
||||
* Project on each); absent until the first.
|
||||
*/
|
||||
needsGitHubRepo?: { hasRepos: boolean; attempt?: number } | null
|
||||
needsGitHubRepo?: { hasRepos: boolean; unreachable?: boolean; attempt?: number } | null
|
||||
/** Bumped to open the Project picker at its GitHub repositories (CLONE-ACCESS-ERROR's Choose another). */
|
||||
openRepoPickerSignal?: number
|
||||
}
|
||||
@@ -32,10 +32,19 @@ export function orcaCloudNeedsGitHubRepo(args: {
|
||||
pickedRepo: boolean
|
||||
repoCount: number
|
||||
githubProjectCount: number
|
||||
}): { hasRepos: boolean } | null {
|
||||
return args.cloudSelected && args.folderProject && !args.pickedRepo
|
||||
? { hasRepos: args.repoCount > 0 || args.githubProjectCount > 0 }
|
||||
: null
|
||||
/**
|
||||
* UNREACHABLE-REPO-NO-ROUTE: a GitHub project cloned only on a remote server whose repository
|
||||
* Orca Cloud can't reach. The line says so and offers More access, instead of the folder wording.
|
||||
*/
|
||||
remoteServerProject?: boolean
|
||||
}): { hasRepos: boolean; unreachable?: true } | null {
|
||||
if (!args.cloudSelected || !args.folderProject || args.pickedRepo) {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
hasRepos: args.repoCount > 0 || args.githubProjectCount > 0,
|
||||
...(args.remoteServerProject ? { unreachable: true as const } : {})
|
||||
}
|
||||
}
|
||||
|
||||
/** A project that can start in Orca Cloud: git, with a GitHub remote (or one not read yet). */
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
// @vitest-environment happy-dom
|
||||
// UNREACHABLE-REPO-NO-ROUTE: Orca Cloud on a GitHub project cloned only on a remote server, whose
|
||||
// repository the team's GitHub access doesn't cover. Create waits, and the line under Project says
|
||||
// why (not the folder wording) with More access; the Project picker lists repositories to pick.
|
||||
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { orcaCloudNeedsGitHubRepo } from './cloud-start-view'
|
||||
import { OrcaCloudNeedsRepoLine } from './OrcaCloudNeedsRepoLine'
|
||||
|
||||
afterEach(cleanup)
|
||||
|
||||
const base = {
|
||||
cloudSelected: true,
|
||||
folderProject: true,
|
||||
pickedRepo: false,
|
||||
repoCount: 2,
|
||||
githubProjectCount: 1
|
||||
}
|
||||
|
||||
describe('unreachable repository on a remote-server project', () => {
|
||||
it('marks the wait as unreachable only for a remote-server project', () => {
|
||||
expect(orcaCloudNeedsGitHubRepo({ ...base, remoteServerProject: true })).toEqual({
|
||||
hasRepos: true,
|
||||
unreachable: true
|
||||
})
|
||||
expect(orcaCloudNeedsGitHubRepo(base)).toEqual({ hasRepos: true })
|
||||
// Reachable (auto-picked): nothing waits.
|
||||
expect(
|
||||
orcaCloudNeedsGitHubRepo({ ...base, remoteServerProject: true, pickedRepo: true })
|
||||
).toBeNull()
|
||||
})
|
||||
|
||||
it('says Orca Cloud cannot reach the repository and offers Give Orca access, in a muted tone', () => {
|
||||
const onMoreAccess = vi.fn()
|
||||
render(
|
||||
<OrcaCloudNeedsRepoLine
|
||||
hasRepos
|
||||
onAddRepository={() => {}}
|
||||
unreachable
|
||||
onMoreAccess={onMoreAccess}
|
||||
/>
|
||||
)
|
||||
expect(screen.getByRole('alert').textContent).toContain('can’t reach this project’s')
|
||||
expect(screen.queryByText('Orca Cloud needs a GitHub repository.')).toBeNull()
|
||||
// A fixable access precondition, not an error: never red.
|
||||
expect(screen.getByRole('alert').querySelector('p')?.className).not.toContain('destructive')
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Give Orca access' }))
|
||||
expect(onMoreAccess).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('a folder keeps its own wording', () => {
|
||||
render(<OrcaCloudNeedsRepoLine hasRepos onAddRepository={() => {}} />)
|
||||
expect(screen.getByRole('alert').textContent).toBe('Orca Cloud needs a GitHub repository.')
|
||||
})
|
||||
})
|
||||
@@ -34,6 +34,8 @@ export type CloudOnlyRepo = {
|
||||
* cloned only on a remote server (RUNON-NO-NEW-SANDBOX). Create waits until one is picked.
|
||||
*/
|
||||
needsGitHubPick: boolean
|
||||
/** A GitHub project cloned only on a remote server (RUNON-NO-NEW-SANDBOX). */
|
||||
remoteServerProject: boolean
|
||||
/** The GitHub row is done for a repository listed from the team's installations. */
|
||||
githubStep: CloudSetupStep | null
|
||||
pick: (fullName: string) => void
|
||||
@@ -140,6 +142,7 @@ export function useCloudOnlyRepo({
|
||||
githubStep: githubStep ?? folderStep ?? (remoteServer ? remoteServerStep() : null),
|
||||
folderProject,
|
||||
needsGitHubPick: folderProject || remoteServer,
|
||||
remoteServerProject: remoteServer,
|
||||
pick,
|
||||
clear,
|
||||
moreAccess,
|
||||
@@ -159,8 +162,8 @@ function remoteServerStep(): CloudSetupStep {
|
||||
label: translate('orcaCloud.setup.github.repositoryLabel', 'GitHub repository'),
|
||||
status: translate('orcaCloud.setup.github.neededStatus', 'Needed'),
|
||||
detail: translate(
|
||||
'components.newWorkspace.cloudOnly.remoteServerDetail',
|
||||
'Orca Cloud starts from a GitHub repository and can’t reach this project’s. Give Orca access with More access, or pick another repository.'
|
||||
'components.newWorkspace.cloudOnly.remoteServerUnreachable',
|
||||
'Orca Cloud starts from a GitHub repository and can’t reach this project’s. Give Orca access to it, or pick another repository.'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,6 +222,7 @@ export function useCloudStartComposer({
|
||||
needsGitHubRepo: orcaCloudNeedsGitHubRepo({
|
||||
cloudSelected,
|
||||
folderProject: cloudOnly.needsGitHubPick,
|
||||
remoteServerProject: cloudOnly.remoteServerProject,
|
||||
pickedRepo: cloudOnly.picked !== null,
|
||||
repoCount: cloudOnly.repos?.length ?? 0,
|
||||
githubProjectCount
|
||||
|
||||
-7
@@ -2731,13 +2731,6 @@
|
||||
"sendingNotConfigured": "sending diagnostics is not configured for this build"
|
||||
}
|
||||
},
|
||||
"enterprise": {
|
||||
"audit": {
|
||||
"action": {
|
||||
"wsOwnerNotified": "Notified the previous owner about an owner change"
|
||||
}
|
||||
}
|
||||
},
|
||||
"groups": {
|
||||
"orcaCloud": "Orca Cloud"
|
||||
},
|
||||
|
||||
@@ -24287,7 +24287,7 @@
|
||||
"theAgent": "The agent",
|
||||
"showMore": "Show more repositories…",
|
||||
"folderDetail": "Orca Cloud starts from a GitHub repository. This project is a folder on this computer.",
|
||||
"remoteServerDetail": "Orca Cloud starts from a GitHub repository and can’t reach this project’s. Give Orca access with More access, or pick another repository.",
|
||||
"remoteServerUnreachable": "Orca Cloud starts from a GitHub repository and can’t reach this project’s. Give Orca access to it, or pick another repository.",
|
||||
"headingShort": "GitHub",
|
||||
"badge": "Cloud only. Orca Cloud clones it, so no copy on this computer is needed.",
|
||||
"rowPrivate": "{{repo}}, private, cloud only",
|
||||
|
||||
Reference in New Issue
Block a user