feat: load all workspace of a org in one batch (#9934)

* feat: load all workspace of a org in one batch

* fix cloud sync api test failure

---------

Co-authored-by: Kent Wang <kent.wang@konghq.com>
This commit is contained in:
xdm
2026-06-01 11:06:09 +08:00
committed by GitHub
parent a9f12ff847
commit 6695ac8f04
7 changed files with 90 additions and 25 deletions

View File

@@ -66,18 +66,21 @@ const cloudSyncProject = [
id: 'proj_5145140e072d4007a30bfa6630ddae70',
name: 'Collection Project',
rootDocumentId: 'wrk_a7132f924ba7451594ba64ec411c9e13',
teamProjectId: 'proj_org_7ef19d06-5a24-47ca-bc81-3dea011edec2',
teams,
},
{
id: 'proj_5145140e072d4007a30bfa6630ddae71',
name: 'Environment Project',
rootDocumentId: 'wrk_2068a8dfd6914c369073686bb92737ae',
teamProjectId: 'proj_org_7ef19d06-5a24-47ca-bc81-3dea011edec2',
teams,
},
{
id: 'proj_5145140e072d4007a30bfa6630ddae72',
name: 'MCP Project',
rootDocumentId: 'wrk_efab8e758b97459bab2659d8fdcf8627',
teamProjectId: 'proj_org_7ef19d06-5a24-47ca-bc81-3dea011edec2',
teams,
},
];

View File

@@ -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));

View File

@@ -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),

View File

@@ -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);

View File

@@ -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<Operation>;
push: (options: { teamId: string; teamProjectId: string }) => Promise<void>;
remoteBackendProjects: (options: { teamId: string; teamProjectId: string }) => Promise<BackendProjectWithTeam[]>;
remoteBackendProjectsOfTeam: (options: { teamId: string }) => Promise<BackendProjectWithTeamsAndTeamProjectId[]>;
removeBackendProjectsForRoot: (rootDocumentId: string) => Promise<void>;
removeBranch: (branchName: string) => Promise<void>;
removeRemoteBranch: (branchName: string) => Promise<void>;

View File

@@ -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;
}

View File

@@ -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<string, InsomniaFile[]>();
isFetchingUnsyncedFilesRef.current = true;
// set up a map of remoteId to projectId for all cloud sync projects.
const remoteIdToProjectIdMap = new Map<string, string>();
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<string, InsomniaFile[]>();
// 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]);