Fix empty nested import folder fallback (#6719)
* fix: allow empty nested import as folder * Track open-as-folder recovery action and localize its strings Completes the empty-nested-import fallback: adds the open_as_folder action to the existing add_repo_nested_import_action funnel (integer counts only, no paths) so adoption of the recovery path is measurable, and lands the five-locale catalog entries the new UI strings require. Co-authored-by: Orca <help@stably.ai> * Pin runtime-kind mock return to the literal union in nested import flow test Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com> Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
parent
700b492b1b
commit
2c0be62df6
|
|
@ -193,22 +193,26 @@ const AddRepoDialog = React.memo(function AddRepoDialog() {
|
|||
onGitRepoReady: completeGitRepoAdd,
|
||||
setAddProjectBusyLabel
|
||||
})
|
||||
const { handleImportNestedRepos, resetNestedImportFlow, trackNestedBackAction } =
|
||||
useAddRepoNestedImportFlow({
|
||||
nestedAttemptId,
|
||||
nestedScan,
|
||||
nestedSelectedPaths,
|
||||
nestedRuntimeKind,
|
||||
nestedConnectionId,
|
||||
nestedGroupName,
|
||||
nestedImportScanId,
|
||||
activeRuntimeEnvironmentId: selectedRuntimeEnvironmentId,
|
||||
fetchWorktrees,
|
||||
importNestedRepos,
|
||||
getNestedRepoRuntimeKind,
|
||||
onGitRepoReady: completeGitRepoAdd,
|
||||
setIsAdding
|
||||
})
|
||||
const {
|
||||
handleImportNestedRepos,
|
||||
handleOpenNestedRootFolder,
|
||||
resetNestedImportFlow,
|
||||
trackNestedBackAction
|
||||
} = useAddRepoNestedImportFlow({
|
||||
nestedAttemptId,
|
||||
nestedScan,
|
||||
nestedSelectedPaths,
|
||||
nestedRuntimeKind,
|
||||
nestedConnectionId,
|
||||
nestedGroupName,
|
||||
nestedImportScanId,
|
||||
activeRuntimeEnvironmentId: selectedRuntimeEnvironmentId,
|
||||
fetchWorktrees,
|
||||
importNestedRepos,
|
||||
getNestedRepoRuntimeKind,
|
||||
onGitRepoReady: completeGitRepoAdd,
|
||||
setIsAdding
|
||||
})
|
||||
|
||||
const resetState = useCallback(() => {
|
||||
// Why: kill the git clone process if one is running, so backing out
|
||||
|
|
@ -381,6 +385,7 @@ const AddRepoDialog = React.memo(function AddRepoDialog() {
|
|||
onNestedGroupNameChange={setNestedGroupName}
|
||||
onNestedSelectedPathsChange={setNestedSelectedPaths}
|
||||
onImportNestedRepos={(mode) => void handleImportNestedRepos(mode)}
|
||||
onOpenNestedRootFolder={() => void handleOpenNestedRootFolder()}
|
||||
onCreateNameChange={(value) => {
|
||||
setCreateName(value)
|
||||
setCreateError(null)
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ function renderStepContent(overrides: Partial<StepContentProps>): string {
|
|||
onNestedGroupNameChange: vi.fn(),
|
||||
onNestedSelectedPathsChange: vi.fn(),
|
||||
onImportNestedRepos: vi.fn(),
|
||||
onOpenNestedRootFolder: vi.fn(),
|
||||
onCreateNameChange: vi.fn(),
|
||||
onCreateParentChange: vi.fn(),
|
||||
onPickCreateParent: vi.fn(),
|
||||
|
|
@ -120,6 +121,15 @@ describe('AddRepoDialogStepContent nested imports', () => {
|
|||
expect(html).not.toContain('>Import</button>')
|
||||
})
|
||||
|
||||
it('offers opening the parent folder when nested import selection is empty', () => {
|
||||
const html = renderStepContent({ nestedSelectedPaths: new Set() })
|
||||
|
||||
expect(html).toContain('No repositories are selected')
|
||||
expect(html).toContain('Open as Folder')
|
||||
expect(html).toMatch(/<button[^>]*disabled=""[^>]*>No, import separately<\/button>/)
|
||||
expect(html).toMatch(/<button[^>]*disabled=""[^>]*>Yes, import as group<\/button>/)
|
||||
})
|
||||
|
||||
it('offers host browsing for remote create project locations', () => {
|
||||
const html = renderStepContent({
|
||||
step: 'create',
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ type AddRepoDialogStepContentProps = {
|
|||
onNestedGroupNameChange: (name: string) => void
|
||||
onNestedSelectedPathsChange: Dispatch<SetStateAction<Set<string>>>
|
||||
onImportNestedRepos: (mode: 'group' | 'separate') => void
|
||||
onOpenNestedRootFolder: () => void
|
||||
onCreateNameChange: (name: string) => void
|
||||
onCreateParentChange: (parent: string) => void
|
||||
onPickCreateParent: () => void
|
||||
|
|
@ -140,6 +141,7 @@ export function AddRepoDialogStepContent({
|
|||
onNestedGroupNameChange,
|
||||
onNestedSelectedPathsChange,
|
||||
onImportNestedRepos,
|
||||
onOpenNestedRootFolder,
|
||||
onCreateNameChange,
|
||||
onCreateParentChange,
|
||||
onPickCreateParent,
|
||||
|
|
@ -237,6 +239,7 @@ export function AddRepoDialogStepContent({
|
|||
onGroupNameChange={onNestedGroupNameChange}
|
||||
onSelectedPathsChange={onNestedSelectedPathsChange}
|
||||
onImport={onImportNestedRepos}
|
||||
onOpenAsFolder={onOpenNestedRootFolder}
|
||||
onStopScan={onStopNestedScan}
|
||||
/>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ function renderStepMarkup(
|
|||
onGroupNameChange={vi.fn()}
|
||||
onSelectedPathsChange={vi.fn()}
|
||||
onImport={vi.fn()}
|
||||
onOpenAsFolder={vi.fn()}
|
||||
onStopScan={vi.fn()}
|
||||
{...overrides}
|
||||
/>
|
||||
|
|
@ -122,6 +123,7 @@ describe('AddRepoNestedImportStep', () => {
|
|||
onGroupNameChange={vi.fn()}
|
||||
onSelectedPathsChange={vi.fn()}
|
||||
onImport={onImport}
|
||||
onOpenAsFolder={vi.fn()}
|
||||
onStopScan={vi.fn()}
|
||||
/>
|
||||
</Dialog>
|
||||
|
|
@ -162,6 +164,7 @@ describe('AddRepoNestedImportStep', () => {
|
|||
onImport(mode)
|
||||
setIsAdding(true)
|
||||
}}
|
||||
onOpenAsFolder={vi.fn()}
|
||||
onStopScan={vi.fn()}
|
||||
/>
|
||||
</Dialog>
|
||||
|
|
@ -181,4 +184,44 @@ describe('AddRepoNestedImportStep', () => {
|
|||
expect(findButton(host, 'Yes, import as group').querySelector('.animate-spin')).not.toBeNull()
|
||||
expect(findButton(host, 'No, import separately').querySelector('.animate-spin')).toBeNull()
|
||||
})
|
||||
|
||||
it('offers opening the parent folder when no repositories are selected', () => {
|
||||
const onOpenAsFolder = vi.fn()
|
||||
const host = document.createElement('div')
|
||||
container = host
|
||||
document.body.appendChild(host)
|
||||
root = createRoot(host)
|
||||
|
||||
act(() => {
|
||||
root?.render(
|
||||
<TooltipProvider>
|
||||
<Dialog open>
|
||||
<AddRepoNestedImportStep
|
||||
scan={scan}
|
||||
groupName=""
|
||||
selectedPaths={new Set()}
|
||||
isAdding={false}
|
||||
scanInProgress={false}
|
||||
onGroupNameChange={vi.fn()}
|
||||
onSelectedPathsChange={vi.fn()}
|
||||
onImport={vi.fn()}
|
||||
onOpenAsFolder={onOpenAsFolder}
|
||||
onStopScan={vi.fn()}
|
||||
/>
|
||||
</Dialog>
|
||||
</TooltipProvider>
|
||||
)
|
||||
})
|
||||
|
||||
expect(host.textContent).toContain('No repositories are selected')
|
||||
expect(findButton(host, 'No, import separately').disabled).toBe(true)
|
||||
expect(findButton(host, 'Yes, import as group').disabled).toBe(true)
|
||||
expect(findButton(host, 'Open as Folder').disabled).toBe(false)
|
||||
|
||||
act(() => {
|
||||
findButton(host, 'Open as Folder').click()
|
||||
})
|
||||
|
||||
expect(onOpenAsFolder).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ type AddRepoNestedImportStepProps = {
|
|||
onGroupNameChange: (value: string) => void
|
||||
onSelectedPathsChange: Dispatch<SetStateAction<Set<string>>>
|
||||
onImport: (mode: 'group' | 'separate') => void
|
||||
onOpenAsFolder: () => void
|
||||
onStopScan: () => void
|
||||
}
|
||||
|
||||
|
|
@ -32,11 +33,16 @@ export function AddRepoNestedImportStep({
|
|||
onGroupNameChange,
|
||||
onSelectedPathsChange,
|
||||
onImport,
|
||||
onOpenAsFolder,
|
||||
onStopScan
|
||||
}: AddRepoNestedImportStepProps): React.JSX.Element {
|
||||
const folderName = getRuntimePathBasename(scan.selectedPath) || scan.selectedPath
|
||||
const groupNameInputId = useId()
|
||||
const [pendingImportMode, setPendingImportMode] = useState<'group' | 'separate' | null>(null)
|
||||
const [pendingImportMode, setPendingImportMode] = useState<
|
||||
'folder' | 'group' | 'separate' | null
|
||||
>(null)
|
||||
const noRepositoriesSelected = selectedPaths.size === 0
|
||||
const showOpenAsFolderSpinner = isAdding && pendingImportMode === 'folder'
|
||||
const showSeparateSpinner = isAdding && pendingImportMode === 'separate'
|
||||
const showGroupSpinner = isAdding && pendingImportMode === 'group'
|
||||
|
||||
|
|
@ -50,6 +56,10 @@ export function AddRepoNestedImportStep({
|
|||
setPendingImportMode(mode)
|
||||
onImport(mode)
|
||||
}
|
||||
const handleOpenAsFolder = (): void => {
|
||||
setPendingImportMode('folder')
|
||||
onOpenAsFolder()
|
||||
}
|
||||
const repoCountLabel =
|
||||
scan.repos.length === 1
|
||||
? translate('auto.components.sidebar.AddRepoNestedImportStep.8401a7a0d0', '1 repository')
|
||||
|
|
@ -137,10 +147,31 @@ export function AddRepoNestedImportStep({
|
|||
placeholder={folderName}
|
||||
/>
|
||||
</div>
|
||||
{noRepositoriesSelected ? (
|
||||
<p className="shrink-0 text-xs text-muted-foreground">
|
||||
{translate(
|
||||
'auto.components.sidebar.AddRepoNestedImportStep.6149d5203f',
|
||||
'No repositories are selected. Open the parent folder instead to use editor, terminal, and search without Git features.'
|
||||
)}
|
||||
</p>
|
||||
) : null}
|
||||
<div className="flex shrink-0 flex-wrap justify-end gap-2">
|
||||
{noRepositoriesSelected ? (
|
||||
<Button
|
||||
onClick={handleOpenAsFolder}
|
||||
disabled={isAdding || scanInProgress}
|
||||
variant="secondary"
|
||||
>
|
||||
{showOpenAsFolderSpinner ? <Loader2 className="size-3.5 animate-spin" /> : null}
|
||||
{translate(
|
||||
'auto.components.sidebar.AddRepoNestedImportStep.e52454b7f6',
|
||||
'Open as Folder'
|
||||
)}
|
||||
</Button>
|
||||
) : null}
|
||||
<Button
|
||||
onClick={() => handleImport('separate')}
|
||||
disabled={isAdding || scanInProgress || selectedPaths.size === 0}
|
||||
disabled={isAdding || scanInProgress || noRepositoriesSelected}
|
||||
variant="outline"
|
||||
>
|
||||
{showSeparateSpinner ? <Loader2 className="size-3.5 animate-spin" /> : null}
|
||||
|
|
@ -151,7 +182,7 @@ export function AddRepoNestedImportStep({
|
|||
</Button>
|
||||
<Button
|
||||
onClick={() => handleImport('group')}
|
||||
disabled={isAdding || scanInProgress || selectedPaths.size === 0}
|
||||
disabled={isAdding || scanInProgress || noRepositoriesSelected}
|
||||
>
|
||||
{showGroupSpinner ? <Loader2 className="size-3.5 animate-spin" /> : null}
|
||||
{translate(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,155 @@
|
|||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type * as ReactModule from 'react'
|
||||
import type { NestedRepoScanResult, ProjectGroupImportResult, Repo } from '../../../../shared/types'
|
||||
|
||||
vi.mock('react', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof ReactModule>()
|
||||
return {
|
||||
...actual,
|
||||
useCallback: <T extends (...args: never[]) => unknown>(fn: T) => fn,
|
||||
useRef: <T>(value: T) => ({ current: value })
|
||||
}
|
||||
})
|
||||
|
||||
const folderRepo: Repo = {
|
||||
id: 'platform',
|
||||
path: '/workspace/platform',
|
||||
displayName: 'platform',
|
||||
badgeColor: '#999999',
|
||||
addedAt: 1,
|
||||
kind: 'folder'
|
||||
}
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
state: {
|
||||
repos: [] as Repo[],
|
||||
addNonGitFolder: vi.fn(),
|
||||
closeModal: vi.fn(),
|
||||
openModal: vi.fn()
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('@/store', () => {
|
||||
const useAppStore = Object.assign(
|
||||
(selector: (state: typeof mocks.state) => unknown) => selector(mocks.state),
|
||||
{
|
||||
getState: () => mocks.state
|
||||
}
|
||||
)
|
||||
return { useAppStore }
|
||||
})
|
||||
|
||||
vi.mock('sonner', () => ({
|
||||
toast: {
|
||||
error: vi.fn(),
|
||||
warning: vi.fn()
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('@/lib/telemetry', () => ({
|
||||
track: vi.fn()
|
||||
}))
|
||||
|
||||
import { track } from '@/lib/telemetry'
|
||||
import { useAddRepoNestedImportFlow } from './useAddRepoNestedImportFlow'
|
||||
|
||||
const scan: NestedRepoScanResult = {
|
||||
selectedPath: '/workspace/platform',
|
||||
selectedPathKind: 'non_git_folder',
|
||||
repos: [{ path: '/workspace/platform/app', displayName: 'app', depth: 1 }],
|
||||
truncated: false,
|
||||
timedOut: false,
|
||||
stopped: false,
|
||||
durationMs: 3,
|
||||
maxDepth: 3,
|
||||
maxRepos: 100,
|
||||
timeoutMs: null
|
||||
}
|
||||
|
||||
function useTestAddRepoNestedImportFlow(
|
||||
overrides: Partial<Parameters<typeof useAddRepoNestedImportFlow>[0]> = {}
|
||||
): ReturnType<typeof useAddRepoNestedImportFlow> {
|
||||
return useAddRepoNestedImportFlow({
|
||||
nestedAttemptId: 'attempt-1',
|
||||
nestedScan: scan,
|
||||
nestedSelectedPaths: new Set(),
|
||||
nestedRuntimeKind: 'local',
|
||||
nestedConnectionId: null,
|
||||
nestedGroupName: 'platform',
|
||||
nestedImportScanId: 'scan-1',
|
||||
activeRuntimeEnvironmentId: null,
|
||||
fetchWorktrees: vi.fn(),
|
||||
importNestedRepos: vi.fn<() => Promise<ProjectGroupImportResult | null>>(),
|
||||
getNestedRepoRuntimeKind: vi.fn(() => 'local' as const),
|
||||
onGitRepoReady: vi.fn(),
|
||||
setIsAdding: vi.fn(),
|
||||
...overrides
|
||||
})
|
||||
}
|
||||
|
||||
describe('useAddRepoNestedImportFlow open folder fallback', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mocks.state.repos = []
|
||||
mocks.state.addNonGitFolder.mockResolvedValue(folderRepo)
|
||||
})
|
||||
|
||||
it('opens the scanned local root through the existing non-git folder flow', async () => {
|
||||
const setIsAdding = vi.fn()
|
||||
const { handleOpenNestedRootFolder } = useTestAddRepoNestedImportFlow({ setIsAdding })
|
||||
|
||||
await handleOpenNestedRootFolder()
|
||||
|
||||
expect(mocks.state.addNonGitFolder).toHaveBeenCalledWith('/workspace/platform', {
|
||||
runtimeEnvironmentId: null
|
||||
})
|
||||
expect(mocks.state.closeModal).toHaveBeenCalledTimes(1)
|
||||
expect(setIsAdding).toHaveBeenNthCalledWith(1, true)
|
||||
expect(setIsAdding).toHaveBeenNthCalledWith(2, false)
|
||||
})
|
||||
|
||||
it('keeps runtime folder opens on the runtime that produced the scan', async () => {
|
||||
const { handleOpenNestedRootFolder } = useTestAddRepoNestedImportFlow({
|
||||
activeRuntimeEnvironmentId: 'env-1'
|
||||
})
|
||||
|
||||
await handleOpenNestedRootFolder()
|
||||
|
||||
expect(mocks.state.addNonGitFolder).toHaveBeenCalledWith('/workspace/platform', {
|
||||
runtimeEnvironmentId: 'env-1'
|
||||
})
|
||||
})
|
||||
|
||||
it('tracks the open-as-folder recovery action with zero selection', async () => {
|
||||
const { handleOpenNestedRootFolder } = useTestAddRepoNestedImportFlow()
|
||||
|
||||
await handleOpenNestedRootFolder()
|
||||
|
||||
expect(track).toHaveBeenCalledWith(
|
||||
'add_repo_nested_import_action',
|
||||
expect.objectContaining({
|
||||
action: 'open_as_folder',
|
||||
surface: 'sidebar',
|
||||
runtime_kind: 'local',
|
||||
found_count: 1,
|
||||
selected_count: 0
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('uses the existing SSH non-git folder confirmation for SSH scans', async () => {
|
||||
const { handleOpenNestedRootFolder } = useTestAddRepoNestedImportFlow({
|
||||
nestedConnectionId: 'ssh-builder',
|
||||
nestedRuntimeKind: 'ssh'
|
||||
})
|
||||
|
||||
await handleOpenNestedRootFolder()
|
||||
|
||||
expect(mocks.state.addNonGitFolder).not.toHaveBeenCalled()
|
||||
expect(mocks.state.closeModal).toHaveBeenCalledTimes(1)
|
||||
expect(mocks.state.openModal).toHaveBeenCalledWith('confirm-non-git-folder', {
|
||||
folderPath: '/workspace/platform',
|
||||
connectionId: 'ssh-builder'
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -50,6 +50,7 @@ export function useAddRepoNestedImportFlow({
|
|||
setIsAdding: (isAdding: boolean) => void
|
||||
}): {
|
||||
handleImportNestedRepos: (mode: 'group' | 'separate') => Promise<void>
|
||||
handleOpenNestedRootFolder: () => Promise<void>
|
||||
resetNestedImportFlow: () => void
|
||||
trackNestedBackAction: () => void
|
||||
} {
|
||||
|
|
@ -236,5 +237,69 @@ export function useAddRepoNestedImportFlow({
|
|||
]
|
||||
)
|
||||
|
||||
return { handleImportNestedRepos, resetNestedImportFlow, trackNestedBackAction }
|
||||
const handleOpenNestedRootFolder = useCallback(async (): Promise<void> => {
|
||||
if (!nestedScan) {
|
||||
return
|
||||
}
|
||||
const gen = ++nestedImportGenRef.current
|
||||
const path = nestedScan.selectedPath
|
||||
if (nestedAttemptId) {
|
||||
track(
|
||||
'add_repo_nested_import_action',
|
||||
buildNestedRepoImportActionTelemetry({
|
||||
attemptId: nestedAttemptId,
|
||||
surface: 'sidebar',
|
||||
runtimeKind: nestedRuntimeKind ?? getNestedRepoRuntimeKind(nestedConnectionId),
|
||||
action: 'open_as_folder',
|
||||
foundCount: nestedScan.repos.length,
|
||||
selectedCount: nestedSelectedPaths.size
|
||||
})
|
||||
)
|
||||
}
|
||||
setIsAdding(true)
|
||||
try {
|
||||
const state = useAppStore.getState()
|
||||
if (nestedConnectionId) {
|
||||
state.closeModal()
|
||||
state.openModal('confirm-non-git-folder', {
|
||||
folderPath: path,
|
||||
connectionId: nestedConnectionId
|
||||
})
|
||||
return
|
||||
}
|
||||
const repo = await state.addNonGitFolder(path, {
|
||||
runtimeEnvironmentId: activeRuntimeEnvironmentId?.trim() || null
|
||||
})
|
||||
if (gen !== nestedImportGenRef.current) {
|
||||
return
|
||||
}
|
||||
if (repo) {
|
||||
useAppStore.getState().closeModal()
|
||||
}
|
||||
} catch (err) {
|
||||
if (gen === nestedImportGenRef.current) {
|
||||
toast.error(err instanceof Error ? err.message : String(err))
|
||||
}
|
||||
} finally {
|
||||
if (gen === nestedImportGenRef.current) {
|
||||
setIsAdding(false)
|
||||
}
|
||||
}
|
||||
}, [
|
||||
activeRuntimeEnvironmentId,
|
||||
getNestedRepoRuntimeKind,
|
||||
nestedAttemptId,
|
||||
nestedConnectionId,
|
||||
nestedRuntimeKind,
|
||||
nestedScan,
|
||||
nestedSelectedPaths.size,
|
||||
setIsAdding
|
||||
])
|
||||
|
||||
return {
|
||||
handleImportNestedRepos,
|
||||
handleOpenNestedRootFolder,
|
||||
resetNestedImportFlow,
|
||||
trackNestedBackAction
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3523,7 +3523,9 @@
|
|||
"8401a7a0d0": "1 repository",
|
||||
"d4f1df62ef": "{{value0}} repositories",
|
||||
"b4263a2ac4": "Found {{value0}} in {{value1}}.",
|
||||
"24eda6c8b2": "Scanning... {{value0}}"
|
||||
"24eda6c8b2": "Scanning... {{value0}}",
|
||||
"6149d5203f": "No repositories are selected. Open the parent folder instead to use editor, terminal, and search without Git features.",
|
||||
"e52454b7f6": "Open as Folder"
|
||||
},
|
||||
"AddRepoRemoteStep": {
|
||||
"5b205b5281": "Stop scan",
|
||||
|
|
|
|||
|
|
@ -3508,7 +3508,9 @@
|
|||
"8401a7a0d0": "1 repositorio",
|
||||
"d4f1df62ef": "{{value0}} repositorios",
|
||||
"b4263a2ac4": "Se encontraron {{value0}} en {{value1}}.",
|
||||
"24eda6c8b2": "Explorando... {{value0}}"
|
||||
"24eda6c8b2": "Explorando... {{value0}}",
|
||||
"6149d5203f": "No hay repositorios seleccionados. Abre la carpeta principal para usar el editor, la terminal y la búsqueda sin funciones de Git.",
|
||||
"e52454b7f6": "Abrir como carpeta"
|
||||
},
|
||||
"AddRepoRemoteStep": {
|
||||
"5b205b5281": "Detener escaneo",
|
||||
|
|
|
|||
|
|
@ -3489,7 +3489,9 @@
|
|||
"8401a7a0d0": "1 個の repos",
|
||||
"d4f1df62ef": "{{value0}} 個の repos",
|
||||
"b4263a2ac4": "{{value1}} で {{value0}} が見つかりました。",
|
||||
"24eda6c8b2": "スキャン中... {{value0}}"
|
||||
"24eda6c8b2": "スキャン中... {{value0}}",
|
||||
"6149d5203f": "選択されている repos がありません。代わりに親フォルダーを開くと、Git 機能なしでエディター、terminal、検索を利用できます。",
|
||||
"e52454b7f6": "フォルダーとして開く"
|
||||
},
|
||||
"AddRepoRemoteStep": {
|
||||
"5b205b5281": "スキャンの停止",
|
||||
|
|
|
|||
|
|
@ -3489,7 +3489,9 @@
|
|||
"8401a7a0d0": "repos 1개",
|
||||
"d4f1df62ef": "repos {{value0}}개",
|
||||
"b4263a2ac4": "{{value1}}에서 {{value0}}을(를) 찾았습니다.",
|
||||
"24eda6c8b2": "스캔 중... {{value0}}"
|
||||
"24eda6c8b2": "스캔 중... {{value0}}",
|
||||
"6149d5203f": "선택된 repos가 없습니다. 대신 상위 폴더를 열면 Git 기능 없이 편집기, terminal, 검색을 사용할 수 있습니다.",
|
||||
"e52454b7f6": "폴더로 열기"
|
||||
},
|
||||
"AddRepoRemoteStep": {
|
||||
"5b205b5281": "스캔 중지",
|
||||
|
|
|
|||
|
|
@ -3489,7 +3489,9 @@
|
|||
"8401a7a0d0": "1 个仓库",
|
||||
"d4f1df62ef": "{{value0}} 个仓库",
|
||||
"b4263a2ac4": "在 {{value1}} 中找到 {{value0}}。",
|
||||
"24eda6c8b2": "正在扫描... {{value0}}"
|
||||
"24eda6c8b2": "正在扫描... {{value0}}",
|
||||
"6149d5203f": "未选择任何仓库。改为打开父文件夹,即可在没有 Git 功能的情况下使用编辑器、终端和搜索。",
|
||||
"e52454b7f6": "作为文件夹打开"
|
||||
},
|
||||
"AddRepoRemoteStep": {
|
||||
"5b205b5281": "停止扫描",
|
||||
|
|
|
|||
|
|
@ -20,7 +20,12 @@ export const NESTED_REPO_SCAN_RESULTS = [
|
|||
] as const
|
||||
export type NestedRepoScanTelemetryResult = (typeof NESTED_REPO_SCAN_RESULTS)[number]
|
||||
|
||||
export const NESTED_REPO_IMPORT_ACTIONS = ['import_group', 'import_separate', 'back'] as const
|
||||
export const NESTED_REPO_IMPORT_ACTIONS = [
|
||||
'import_group',
|
||||
'import_separate',
|
||||
'open_as_folder',
|
||||
'back'
|
||||
] as const
|
||||
export type NestedRepoImportTelemetryAction = (typeof NESTED_REPO_IMPORT_ACTIONS)[number]
|
||||
|
||||
export const NESTED_REPO_IMPORT_OUTCOMES = ['success', 'partial_failure', 'failed'] as const
|
||||
|
|
|
|||
Loading…
Reference in New Issue