From d7631ba2caa90496f3edb1c775b20bf9315f9462 Mon Sep 17 00:00:00 2001 From: xiaodemen Date: Tue, 19 May 2026 18:07:45 +0800 Subject: [PATCH] feat: load all workspace of a org in one batch --- packages/insomnia/src/common/project.ts | 4 ++ packages/insomnia/src/entry.preload.ts | 1 + .../insomnia/src/main/cloud-sync/core/vcs.ts | 38 ++++++++++++ packages/insomnia/src/main/cloud-sync/ipc.ts | 2 + packages/insomnia/src/sync/types.ts | 5 ++ .../project-navigation-sidebar.tsx | 62 +++++++++++-------- 6 files changed, 87 insertions(+), 25 deletions(-) diff --git a/packages/insomnia/src/common/project.ts b/packages/insomnia/src/common/project.ts index f5c98d12e58..609d26fc11b 100644 --- a/packages/insomnia/src/common/project.ts +++ b/packages/insomnia/src/common/project.ts @@ -329,6 +329,10 @@ export const getAllRemoteBackendProjectsByProjectId = async ({ return window.main.sync.remoteBackendProjects({ teamId: organizationId, teamProjectId }); }; +export const getAllRemoteBackendProjectsOfOrg = async ({ organizationId }: { organizationId: string }) => { + return window.main.sync.remoteBackendProjectsOfTeam({ teamId: organizationId }); +}; + export const getUnsyncedRemoteWorkspaces = (remoteFiles: InsomniaFile[], workspaces: Workspace[]) => remoteFiles.filter(remoteFile => !workspaces.find(w => w._id === remoteFile.id)); diff --git a/packages/insomnia/src/entry.preload.ts b/packages/insomnia/src/entry.preload.ts index d2221a072fc..e2ae1bb713a 100644 --- a/packages/insomnia/src/entry.preload.ts +++ b/packages/insomnia/src/entry.preload.ts @@ -172,6 +172,7 @@ const sync: SyncBridgeAPI = { pullRemoteBackendProject: options => invokeWithNormalizedError('sync.pullRemoteBackendProject', options), push: (...args) => invokeSyncMethod('push', ...args), remoteBackendProjects: (...args) => invokeSyncMethod('remoteBackendProjects', ...args), + remoteBackendProjectsOfTeam: (...args) => invokeSyncMethod('remoteBackendProjectsOfTeam', ...args), removeBackendProjectsForRoot: (...args) => invokeSyncMethod('removeBackendProjectsForRoot', ...args), removeBranch: (...args) => invokeSyncMethod('removeBranch', ...args), removeRemoteBranch: (...args) => invokeSyncMethod('removeRemoteBranch', ...args), diff --git a/packages/insomnia/src/main/cloud-sync/core/vcs.ts b/packages/insomnia/src/main/cloud-sync/core/vcs.ts index a39c77e4979..92c9dc722b1 100644 --- a/packages/insomnia/src/main/cloud-sync/core/vcs.ts +++ b/packages/insomnia/src/main/cloud-sync/core/vcs.ts @@ -17,6 +17,7 @@ import { generateId } from '../../../common/misc'; import type { BackendProject, BackendProjectWithTeams, + BackendProjectWithTeamsAndTeamProjectId, Branch, DocumentKey, Head, @@ -205,6 +206,43 @@ export class VCS { })); } + async remoteBackendProjectsOfTeam({ teamId }: { teamId: string }) { + console.log(`[remoteBackendProjectsOfTeam] Fetching remote workspaces for teamId=${teamId}`); + + const { projects } = await this._runGraphQL<{ projects: BackendProjectWithTeamsAndTeamProjectId[] }>( + ` + query ($teamId: ID, $allProjects: Boolean) { + projects(teamId: $teamId, allProjects: $allProjects) { + id + name + rootDocumentId + teamProjectId + teams { + id + name + } + } + } + `, + { + teamId, + allProjects: true, + }, + 'projects', + ); + + console.log(`[remoteBackendProjectsOfTeam] Fetched ${projects.length} remote workspaces`); + + return projects.map(backend => ({ + id: backend.id, + name: backend.name, + rootDocumentId: backend.rootDocumentId, + teamProjectId: backend.teamProjectId, + // A backend project is guaranteed to exist on exactly one team + team: backend.teams[0], + })); + } + async blobFromLastSnapshot(key: string) { const branch = await this._getCurrentBranch(); const snapshot = await this._getLatestSnapshot(branch.name); diff --git a/packages/insomnia/src/main/cloud-sync/ipc.ts b/packages/insomnia/src/main/cloud-sync/ipc.ts index 2e2ab7979c1..215b064497d 100644 --- a/packages/insomnia/src/main/cloud-sync/ipc.ts +++ b/packages/insomnia/src/main/cloud-sync/ipc.ts @@ -3,6 +3,7 @@ import type { IpcRendererEvent } from 'electron'; import type { BackendProject, BackendProjectWithTeam, + BackendProjectWithTeamsAndTeamProjectId, Compare, MergeConflict, Snapshot, @@ -43,6 +44,7 @@ export interface SyncBridgeMethods { }) => Promise; push: (options: { teamId: string; teamProjectId: string }) => Promise; remoteBackendProjects: (options: { teamId: string; teamProjectId: string }) => Promise; + remoteBackendProjectsOfTeam: (options: { teamId: string }) => Promise; removeBackendProjectsForRoot: (rootDocumentId: string) => Promise; removeBranch: (branchName: string) => Promise; removeRemoteBranch: (branchName: string) => Promise; diff --git a/packages/insomnia/src/sync/types.ts b/packages/insomnia/src/sync/types.ts index a9ff50e056e..38f777c8edf 100644 --- a/packages/insomnia/src/sync/types.ts +++ b/packages/insomnia/src/sync/types.ts @@ -15,6 +15,11 @@ export interface BackendProjectWithTeams extends BackendProject { teams: Team[]; } +export interface BackendProjectWithTeamsAndTeamProjectId extends BackendProject { + teams: Team[]; + teamProjectId: string; +} + export interface BackendProjectWithTeam extends BackendProject { team: Team; } diff --git a/packages/insomnia/src/ui/components/sidebar/project-navigation-sidebar/project-navigation-sidebar.tsx b/packages/insomnia/src/ui/components/sidebar/project-navigation-sidebar/project-navigation-sidebar.tsx index 63fd0afcac2..41787a6afd9 100644 --- a/packages/insomnia/src/ui/components/sidebar/project-navigation-sidebar/project-navigation-sidebar.tsx +++ b/packages/insomnia/src/ui/components/sidebar/project-navigation-sidebar/project-navigation-sidebar.tsx @@ -8,11 +8,7 @@ import * as reactUse from 'react-use'; import { Button as BasicButton } from '~/basic-components/button'; import type { SortOrder } from '~/common/constants'; import { fuzzyMatchAll } from '~/common/misc'; -import { - getAllRemoteBackendProjectsByProjectId, - getUnsyncedRemoteWorkspaces, - type InsomniaFile, -} from '~/common/project'; +import { getAllRemoteBackendProjectsOfOrg, getUnsyncedRemoteWorkspaces, type InsomniaFile } from '~/common/project'; import { sortMethodMap } from '~/common/sorting'; import type { RequestGroup, Workspace } from '~/insomnia-data'; import { models, services } from '~/insomnia-data'; @@ -256,32 +252,48 @@ export const ProjectNavigationSidebar = ({ const cloudSyncProjectIds = cloudSyncProjectIdsKey.split(','); const result = new Map(); isFetchingUnsyncedFilesRef.current = true; + + // set up a map of remoteId to projectId for all cloud sync projects. + const remoteIdToProjectIdMap = new Map(); for (const projectId of cloudSyncProjectIds) { - try { - const targetProject = await services.project.get(projectId); - if (targetProject && 'remoteId' in targetProject && targetProject.remoteId) { - const files = await getAllRemoteBackendProjectsByProjectId({ - teamProjectId: targetProject.remoteId, - organizationId, + const project = await services.project.get(projectId); + if (project && 'remoteId' in project && project.remoteId) { + remoteIdToProjectIdMap.set(project.remoteId, projectId); + } + } + + try { + const files = await getAllRemoteBackendProjectsOfOrg({ organizationId }); + const filesByProjectId = new Map(); + // group files by projectId + for (const file of files) { + const projectId = remoteIdToProjectIdMap.get(file.teamProjectId); + if (projectId) { + if (!filesByProjectId.has(projectId)) { + filesByProjectId.set(projectId, []); + } + filesByProjectId.get(projectId)?.push({ + id: file.rootDocumentId, + name: file.name, + scope: 'unsynced', + label: 'Unsynced', + remoteId: file.id, + created: 0, + lastModifiedTimestamp: 0, }); - result.set( - projectId, - files.map(f => ({ - id: f.rootDocumentId, - name: f.name, - scope: 'unsynced', - label: 'Unsynced', - remoteId: f.id, - created: 0, - lastModifiedTimestamp: 0, - })), - ); } - } catch (error) { - console.error(`Failed to fetch unsynced files for project ${projectId}`, error); + } + + for (const [projectId, files] of filesByProjectId.entries()) { + result.set(projectId, files); + } + } catch (error) { + console.error(`Failed to fetch unsynced files for organization ${organizationId}`, error); + for (const projectId of cloudSyncProjectIds) { result.set(projectId, []); } } + isFetchingUnsyncedFilesRef.current = false; return setUnsyncedFilesByProjectId(result); }, [organizationId, cloudSyncProjectIdsKey]);