feat(Git Sync): improve git migration onboarding UX and local file system access [INS-2462] (#9890)

* feat(Migration): enhance migration summary with total projects count and improve UI feedback

* fix(Migration): clarify update instructions and improve user messaging

* style(ManualCommitForm): adjust text sizes for improved readability

* style(StagingModal): adjust layout and spacing for improved UI consistency

* fix(ManualCommitForm): update clipboard text to include 'cd' command for easier navigation

* fix(ProjectSettingsForm): update repository path copy functionality and add option to open in file system

* fix(ManualCommitForm): enhance file system interaction with tooltips for better user guidance

* fix(ProjectSettingsForm): add tooltip for 'Open in file system' button to enhance user guidance

* fix(GitProjectSyncDropdown): add 'Open folder' action to sync dropdown for easier access to repository path

* fix(Component): display relative path of current issue in modal for better context

* fix(git-service): count only successfully migrated projects in totalProjects

* fix(project-settings-form): platform-aware shell quoting for cd command

* fix(git-project-staging-modal): platform-aware shell quoting for cd command

* fix(project-settings-form): update aria-label to reflect cd command clipboard content

* fix(git-project-staging-modal): update aria-label to reflect shell command clipboard content

* fix(ManualCommitForm): replace tooltip with dialog for enhanced information display

* fix(MigrationView): update migrated count calculation to reflect total projects

---------

Co-authored-by: James Gatz <jamesgatzos@gmail.com>
This commit is contained in:
Pavlos Koutoglou
2026-05-04 21:41:17 +03:00
committed by GitHub
parent 4c7385292b
commit 71dc5c9370
8 changed files with 138 additions and 77 deletions

View File

@@ -2886,6 +2886,7 @@ async function getCurrentBranchByRepositoryId({
export interface MigrationSummary {
logs: string[];
failedProjects: { id: string; name: string }[];
totalProjects: number;
}
export async function runAllGitRepoMigrations(): Promise<MigrationSummary> {
@@ -2895,7 +2896,7 @@ export async function runAllGitRepoMigrations(): Promise<MigrationSummary> {
const allProjects = await services.project.all();
const gitProjects = allProjects.filter((p): p is GitProject => models.project.isConnectedGitProject(p));
if (gitProjects.length === 0) return { logs, failedProjects };
if (gitProjects.length === 0) return { logs, failedProjects, totalProjects: 0 };
// Batch-fetch all git repositories in one query instead of N individual lookups.
const repoIds = gitProjects.map(p => models.project.getEffectiveRepoId(p)).filter(Boolean) as string[];
@@ -2913,6 +2914,8 @@ export async function runAllGitRepoMigrations(): Promise<MigrationSummary> {
`${ts()} [INFO] Starting migration v${CURRENT_MIGRATION_VERSION} for ${gitProjects.length} repo(s): ${projectList}`,
);
let migratedCount = 0;
await Promise.all(
gitProjects.map(async project => {
const gitRepository = repoById.get(models.project.getEffectiveRepoId(project)!);
@@ -2933,6 +2936,8 @@ export async function runAllGitRepoMigrations(): Promise<MigrationSummary> {
const success = await migrateRepoStructureIfNeeded(baseDir, project._id, repoId, logger);
if (!success) {
failedProjects.push({ id: project._id, name: project.name });
} else {
migratedCount++;
}
}),
);
@@ -2965,7 +2970,7 @@ export async function runAllGitRepoMigrations(): Promise<MigrationSummary> {
}),
);
return { logs, failedProjects };
return { logs, failedProjects, totalProjects: migratedCount };
}
export interface GitServiceAPI {

View File

@@ -9,24 +9,37 @@ import git_migration from '~/ui/images/git-migration/git.png';
type MigrationStatus = 'default' | 'running' | 'completed' | 'partiallyCompleted' | 'error';
const MIN_DISPLAY_MS = 3000;
const MigrationView = () => {
const [status, setStatus] = useState<MigrationStatus>('default');
const [migrationLogs, setMigrationLogs] = useState<string[]>([]);
const [failedProjects, setFailedProjects] = useState<{ id: string; name: string }[]>([]);
const [migratedCount, setMigratedCount] = useState(0);
const handleMigration = () => {
setStatus('running');
const startTime = Date.now();
window.main.git
.runAllGitRepoMigrations()
.then((result: { logs: string[]; failedProjects: { id: string; name: string }[] }) => {
setMigrationLogs(result.logs);
setFailedProjects(result.failedProjects);
setStatus(result.failedProjects.length > 0 ? 'partiallyCompleted' : 'completed');
.then((result: { logs: string[]; failedProjects: { id: string; name: string }[]; totalProjects: number }) => {
const elapsed = Date.now() - startTime;
const remaining = Math.max(0, MIN_DISPLAY_MS - elapsed);
setTimeout(() => {
setMigrationLogs(result.logs);
setFailedProjects(result.failedProjects);
setMigratedCount(result.totalProjects);
setStatus(result.failedProjects.length > 0 ? 'partiallyCompleted' : 'completed');
}, remaining);
})
.catch((err: unknown) => {
const elapsed = Date.now() - startTime;
const remaining = Math.max(0, MIN_DISPLAY_MS - elapsed);
const errorMsg = err instanceof Error ? err.message : 'An unexpected error occurred.';
setMigrationLogs(prev => [...prev, `[ERROR] ${errorMsg}`]);
setStatus('error');
setTimeout(() => {
setMigrationLogs(prev => [...prev, `[ERROR] ${errorMsg}`]);
setStatus('error');
}, remaining);
});
};
@@ -39,7 +52,8 @@ const MigrationView = () => {
<div className="flex h-full min-h-[500px] w-[600px] flex-col items-center justify-center">
<div className="relative flex w-full flex-col items-center justify-center gap-(--padding-sm) rounded-md border border-solid border-(--hl-sm) bg-(--hl-xs) p-8">
<div className="relative flex min-h-[150px] flex-col justify-between gap-4 text-left text-(--color-font)">
<h1 className="text-xl">
<h1 className="flex items-center gap-2 text-xl">
{isUpdateCompletedSuccessfully && <i className="fa fa-check-circle text-emerald-500" />}
{isUpdateCompletedSuccessfully
? 'Update Successful'
: isUpdateErrored
@@ -51,8 +65,9 @@ const MigrationView = () => {
{isUpdateCompletedSuccessfully ? (
<p className="text-sm">
Your file system has been successfully updated. Now you can explore all of your Insomnia files on your
local system and use git on your CLI to manage changes.
All {migratedCount} Insomnia git project{migratedCount !== 1 ? 's' : ''} on your local system have been
updated. You can now explore them, use git from your favourite CLI, or modify them from any other tool of
your choice.
</p>
) : isUpdateCompletedWithErrors ? (
<>
@@ -73,7 +88,7 @@ const MigrationView = () => {
<>
<p className="text-sm">We hit an unexpected error while updating your file system. Please try again.</p>
<p className="text-sm text-[#828282]">
Having trouble and need to contact us, or back up to an old version? See our docs
Having trouble and need to contact us, or back up to an old version? See our{' '}
<Link className="underline" to="https://developer.konghq.com/insomnia/git-sync/">
docs.
</Link>
@@ -82,23 +97,20 @@ const MigrationView = () => {
) : (
<>
<p className="text-sm">
In order to continue with this update, we need to adjust your local file system. This is required to
enable managing Insomnia changes using git on the CLI.
In order to continue with this update, we need to adjust your local projects. This is required to enable
managing Insomnia changes using git on the CLI.
</p>
<p className="text-sm">
{isUpdateRunning
? 'Note: Your data is safe and the update only takes seconds.'
: 'Note: This update does NOT change your data and only affects how your local Insomnia files are stored.'}
: 'Note: This update does NOT affect any ongoing work or pending changes, and only affects how your local Insomnia files are stored.'}
</p>
</>
)}
<div className="flex h-[32px] w-full justify-end">
{isUpdateCompletedSuccessfully ? (
<Link
className="h-[32px] rounded-xs border border-solid border-(--hl-md) bg-(--color-surprise) px-3 py-2 text-sm text-(--color-font-surprise) transition-colors hover:bg-(--color-surprise)/90 hover:no-underline"
to="/organization"
>
<Link className="border border-solid border-(--hl-md) bg-(--color-surprise) font-semibold text-(--color-font-surprise) flex h-full items-center justify-center gap-2 rounded-md px-4 py-2 text-sm ring-1 ring-transparent transition-all focus:ring-(--hl-md) focus:ring-inset aria-pressed:opacity-80" to="/organization">
Open Insomnia
</Link>
) : isUpdateCompletedWithErrors ? (
@@ -111,10 +123,7 @@ const MigrationView = () => {
<i className="fa fa-copy" />
Copy Error Logs
</CopyButton>
<Link
className="h-[32px] rounded-xs border border-solid border-(--hl-md) bg-(--color-surprise) px-3 py-2 text-sm text-(--color-font-surprise) transition-colors hover:bg-(--color-surprise)/90 hover:no-underline"
to="/organization"
>
<Link className="border border-solid border-(--hl-md) bg-(--color-surprise) font-semibold text-(--color-font-surprise) flex h-full items-center justify-center gap-2 rounded-md px-4 py-2 text-sm ring-1 ring-transparent transition-all focus:ring-(--hl-md) focus:ring-inset aria-pressed:opacity-80" to="/organization">
Open Insomnia
</Link>
</div>
@@ -128,20 +137,12 @@ const MigrationView = () => {
<i className="fa fa-copy" />
Copy Error Logs
</CopyButton>
<Button
className="h-[32px] rounded-xs border border-solid border-(--hl-md) bg-(--color-surprise) px-3 py-2 text-sm text-(--color-font-surprise) transition-colors hover:bg-(--color-surprise)/90 hover:no-underline"
onClick={handleMigration}
isDisabled={isUpdateRunning}
>
<Button className="border border-solid border-(--hl-md) bg-(--color-surprise) font-semibold text-(--color-font-surprise) flex h-full items-center justify-center gap-2 rounded-md px-4 py-2 text-sm ring-1 ring-transparent transition-all focus:ring-(--hl-md) focus:ring-inset aria-pressed:opacity-80" onClick={handleMigration} isDisabled={isUpdateRunning}>
{isUpdateRunning ? 'Updating...' : 'Retry Update'}
</Button>
</div>
) : (
<Button
className="h-[32px] rounded-xs border border-solid border-(--hl-md) bg-(--color-surprise) px-3 py-2 text-sm text-(--color-font-surprise) transition-colors hover:bg-(--color-surprise)/90 hover:no-underline"
onClick={handleMigration}
isDisabled={isUpdateRunning}
>
<Button className="border border-solid border-(--hl-md) bg-(--color-surprise) font-semibold text-(--color-font-surprise) flex h-full items-center justify-center gap-2 rounded-md px-4 py-2 text-sm ring-1 ring-transparent transition-all focus:ring-(--hl-md) focus:ring-inset aria-pressed:opacity-80" onClick={handleMigration} isDisabled={isUpdateRunning}>
{isUpdateRunning ? 'Updating...' : 'Update Now'}
</Button>
)}
@@ -181,7 +182,7 @@ const Component = () => {
</div>
<div className="flex w-full justify-end">
<Button
className="h-[32px] rounded-xs border border-solid border-(--hl-md) bg-(--color-surprise) px-3 py-2 text-sm text-(--color-font-surprise) transition-colors hover:bg-(--color-surprise)/90"
className="border border-solid border-(--hl-md) bg-(--color-surprise) font-semibold text-(--color-font-surprise) flex h-full items-center justify-center gap-2 rounded-md px-4 py-2 text-sm ring-1 ring-transparent transition-all focus:ring-(--hl-md) focus:ring-inset aria-pressed:opacity-80"
onClick={() => {
setShowMigrationView(true);
}}

View File

@@ -428,6 +428,13 @@ const Component = () => {
<div className="flex flex-col gap-3">
<h2 className="text-2xl font-semibold text-(--color-font)">{modalText.modalTitle}</h2>
<p className="max-w-2xl text-lg text-(--hl)">{modalText.summary}</p>
{currentIssue.relPath && (
<ul className="list-disc pl-5 text-left text-sm text-(--hl)">
<li>
<span className="font-mono">{currentIssue.relPath}</span>
</li>
</ul>
)}
</div>
<Button
onPress={handleBackToList}

View File

@@ -569,6 +569,10 @@ export const GitProjectSyncDropdown: FC<Props> = ({ gitRepository, activeProject
]
: [];
const repoPath = gitRepository?._id
? window.path.join(window.app.getPath('userData'), 'version-control', 'git', gitRepository._id)
: '';
const gitSyncActions: {
id: string;
label: string;
@@ -576,6 +580,13 @@ export const GitProjectSyncDropdown: FC<Props> = ({ gitRepository, activeProject
isDisabled?: boolean;
action: () => void;
}[] = [
{
id: 'open-folder',
label: 'Open folder',
isDisabled: !repoPath,
icon: 'folder-open',
action: () => window.shell.openPath(repoPath),
},
{
id: 'branches',
label: 'Branches',

View File

@@ -11,6 +11,7 @@ import React, {
import {
Button,
Dialog,
DialogTrigger,
GridList,
GridListItem,
Heading,
@@ -18,6 +19,7 @@ import {
Label,
Modal,
ModalOverlay,
Popover,
TextArea,
TextField,
Tooltip,
@@ -56,6 +58,7 @@ import { showSettingsModal } from '~/ui/components/modals/settings-modal';
import { SvgIcon } from '~/ui/components/svg-icon';
import { useAIFeatureStatus } from '~/ui/hooks/use-organization-features';
import { platform } from '../../../common/platform';
import { DiffEditor } from '../diff-view-editor';
import { Icon } from '../icon';
import { showToast } from '../toast-notification';
@@ -1057,37 +1060,63 @@ const ManualCommitForm: FC<ManualCommitFormProps> = ({
PREVIEW
</span>
<span className="font-semibold">Manage changes on the Git CLI</span>
<DialogTrigger>
<Button
className="flex items-center justify-center rounded-xs p-0.5 text-(--hl) hover:bg-(--hl-xs)"
aria-label="More information"
>
<Icon icon="circle-info" className="size-3.5" />
</Button>
<Popover
offset={8}
className="max-w-xs rounded-md border border-solid border-(--hl-sm) bg-(--color-bg) px-3 py-2 text-sm text-(--color-font) shadow-lg"
>
<Dialog className="outline-none" >
You can now browse Git Sync project files on your local file system and manage changes using your normal
Git workflows.{' '}
<a href="https://developer.konghq.com/insomnia/git-sync/" className="underline">
Learn more
</a>
</Dialog>
</Popover>
</DialogTrigger>
</div>
<p className="mb-3 text-sm text-(--color-font)">
You can now browse Git Sync project files on your local file system and manage changes using your normal Git
workflows.{' '}
<a href="https://developer.konghq.com/insomnia/git-sync/" className="underline">
Learn more
</a>
</p>
<p className="mb-1 font-semibold">Path to this project:</p>
<div className="mb-3 flex items-center justify-between rounded-xs bg-(--hl-xxs) px-2 py-2 font-mono text-(--color-font)">
<p className="mb-1 text-xs font-semibold">Path to this project:</p>
<div className="flex items-center justify-between rounded-xs bg-(--hl-xxs) px-2 py-2 font-mono text-(--color-font)">
<span className="min-w-0 flex-1 truncate" title={repoPath}>
{repoPath}
</span>
<Button
onPress={() => {
window.clipboard.writeText(repoPath);
const cmd =
platform === 'win32'
? `cd "${repoPath.replace(/"/g, '\\"')}"`
: `cd '${repoPath.replace(/'/g, "'\\''")}'`;
window.clipboard.writeText(cmd);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
}}
className="mb-1 flex items-center justify-center rounded-xs p-1 hover:bg-(--hl-xs)"
aria-label="Copy path"
className="flex items-center justify-center rounded-xs p-1 hover:bg-(--hl-xs)"
aria-label="Copy shell command"
>
<Icon icon={copied ? 'check' : 'copy'} className="size-4" />
</Button>
<TooltipTrigger>
<Button
onPress={() => window.shell.openPath(repoPath)}
className="flex items-center justify-center rounded-xs p-1 hover:bg-(--hl-xs)"
aria-label="Open in file system"
>
<Icon icon="folder-open" className="size-4" />
</Button>
<Tooltip
offset={8}
className="rounded-md border border-solid border-(--hl-sm) bg-(--color-bg) px-3 py-2 text-sm text-(--color-font) shadow-lg"
>
Open in file system
</Tooltip>
</TooltipTrigger>
</div>
<Button
onPress={() => window.shell.showItemInFolder(repoPath)}
className="cursor-pointer text-(--hl) underline"
>
Open in file system
</Button>
</div>
</>
);
@@ -1370,8 +1399,8 @@ const OriginalGitProjectStagingModal: FC<
</p>
</div>
)}
<div className="grid h-full grid-cols-[300px_1fr] gap-2 divide-x divide-solid divide-(--hl-md) overflow-hidden">
<div className="flex flex-1 flex-col gap-4 overflow-y-auto p-2">
<div className="grid h-full grid-cols-[350px_1fr] gap-4 divide-x divide-solid divide-(--hl-md) overflow-hidden">
<div className="flex flex-1 flex-col gap-4 overflow-y-auto p-4">
{isGenerateCommitMessagesWithAIEnabled && (
<div className="flex flex-col gap-3 rounded-sm border border-solid border-(--hl-md) p-3">
<h3 className="font-semibold">

View File

@@ -148,7 +148,7 @@ export const GitStagingModal: FC<{ onClose: () => void }> = ({ onClose }) => {
<Icon icon="x" />
</Button>
</div>
<div className="grid h-full grid-cols-[300px_1fr] gap-2 divide-x divide-solid divide-(--hl-md) overflow-hidden">
<div className="grid h-full grid-cols-[350px_1fr] gap-4 divide-x divide-solid divide-(--hl-md) overflow-hidden">
<div className="flex flex-1 flex-col gap-4 overflow-hidden">
<form
onSubmit={e => {

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import { useEffect, useState } from 'react';
import {
Button,
Dialog,
@@ -176,7 +176,7 @@ export const SyncStagingModal = ({ onClose, status, syncItems }: Props) => {
<Icon icon="x" />
</Button>
</div>
<div className="grid h-full grid-cols-[300px_1fr] gap-2 divide-x divide-solid divide-(--hl-md) overflow-hidden">
<div className="grid h-full grid-cols-[350px_1fr] gap-4 divide-x divide-solid divide-(--hl-md) overflow-hidden">
<div className="flex flex-1 flex-col gap-4 overflow-hidden p-2">
<form
onSubmit={e => {

View File

@@ -11,6 +11,8 @@ import {
Select,
SelectValue,
TextField,
Tooltip,
TooltipTrigger,
} from 'react-aria-components';
import { useParams } from 'react-router';
@@ -33,6 +35,7 @@ import { useActiveView } from '~/ui/components/project/utils';
import { useIsLightTheme } from '~/ui/hooks/theme';
import { useIsGitSyncEnabled } from '~/ui/hooks/use-organization-features';
import { platform } from '../../../common/platform';
import { useProjectUpdateActionFetcher } from '../../../routes/organization.$organizationId.project.$projectId.update';
import { Icon } from '../icon';
@@ -323,35 +326,40 @@ export const ProjectSettingsForm: FC<Props> = ({
Learn more
</a>
</div>
<div className="flex items-stretch justify-between gap-2">
<span
title={repoPath}
className="min-w-0 flex-1 truncate rounded-xs border border-solid border-(--hl-sm) bg-(--color-bg) px-2 py-1 font-mono text-base leading-8 text-(--hl-xl)"
>
<div className="flex items-center justify-between rounded-xs bg-(--hl-xxs) px-2 py-2 font-mono text-(--color-font)">
<span className="min-w-0 flex-1 truncate" title={repoPath}>
{repoPath}
</span>
<Button
onPress={() => {
window.clipboard.writeText(repoPath);
const cmd =
platform === 'win32'
? `cd "${repoPath.replace(/"/g, '\\"')}"`
: `cd '${repoPath.replace(/'/g, "'\\''")}'`;
window.clipboard.writeText(cmd);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
}}
className="flex shrink-0 items-center gap-1.5 rounded-xs border border-solid border-(--hl-sm) px-2 text-sm text-(--color-font) transition-colors hover:bg-(--hl-xs)"
aria-label="Copy repository path"
className="flex items-center justify-center rounded-xs p-1 hover:bg-(--hl-xs)"
aria-label="Copy cd command for repository path"
>
<Icon icon={copied ? 'check' : 'copy'} />
<span>{copied ? 'Copied!' : 'Copy'}</span>
</Button>
<Button
onPress={() => {
window.shell.showItemInFolder(repoPath);
}}
className="flex shrink-0 items-center gap-1.5 rounded-xs border border-solid border-(--hl-sm) px-2 text-sm text-(--color-font) transition-colors hover:bg-(--hl-xs)"
aria-label="Open repository folder"
>
<Icon icon={'folder'} />
<span>Open</span>
<Icon icon={copied ? 'check' : 'copy'} className="size-4" />
</Button>
<TooltipTrigger>
<Button
onPress={() => window.shell.openPath(repoPath)}
className="flex items-center justify-center rounded-xs p-1 hover:bg-(--hl-xs)"
aria-label="Open in file system"
>
<Icon icon="folder-open" className="size-4" />
</Button>
<Tooltip
offset={8}
className="rounded-md border border-solid border-(--hl-sm) bg-(--color-bg) px-3 py-2 text-sm text-(--color-font) shadow-lg"
>
Open in file system
</Tooltip>
</TooltipTrigger>
</div>
</div>
</>