Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 125 additions & 0 deletions packages/insomnia/src/common/__tests__/insomnia-v5.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,131 @@ collection: []
expect(parsed.collection[0].name).toBe('Request 1');
});

it('preserves requests at the workspace root alongside pruned folders when filtering', async () => {
const workspace = await services.workspace.create({
_id: 'wrk_mixed_filter',
name: 'Mixed Filter Workspace',
parentId: 'proj_test',
scope: 'collection',
});

await services.environment.create({
_id: 'env_mixed_filter',
name: 'Base Env',
parentId: workspace._id,
data: {},
});

// Request at workspace root (no parent folder).
const rootRequest = await services.request.create({
_id: 'req_root',
name: 'Root Request',
parentId: workspace._id,
url: 'https://api.example.com/root',
method: 'GET',
});

// Unrelated folder + request that must not appear in the export.
const unrelatedFolder = await services.requestGroup.create({
_id: 'fld_unrelated',
name: 'Unrelated Folder',
parentId: workspace._id,
});

await services.request.create({
_id: 'req_skipped',
name: 'Skipped Request',
parentId: unrelatedFolder._id,
url: 'https://api.example.com/skipped',
method: 'GET',
});

const result = await getInsomniaV5DataExport({
workspaceId: workspace._id,
includePrivateEnvironments: false,
requestIds: [rootRequest._id],
});

const parsed = YAML.parse(result);
expect(parsed.collection).toHaveLength(1);
expect(parsed.collection[0].name).toBe('Root Request');
expect(result).not.toContain('Unrelated Folder');
expect(result).not.toContain('Skipped Request');
});

it('only includes folders that are ancestors of selected requests when filtering', async () => {
const workspace = await services.workspace.create({
_id: 'wrk_folder_filter',
name: 'Folder Filter Workspace',
parentId: 'proj_test',
scope: 'collection',
});

await services.environment.create({
_id: 'env_folder_filter',
name: 'Base Env',
parentId: workspace._id,
data: {},
});

// Folder A contains the selected request.
const folderA = await services.requestGroup.create({
_id: 'fld_A',
name: 'Folder A',
parentId: workspace._id,
description: 'folder a docs',
});

const subFolderA = await services.requestGroup.create({
_id: 'fld_A_sub',
name: 'Sub Folder A',
parentId: folderA._id,
description: 'sub folder a docs',
});

const selectedRequest = await services.request.create({
_id: 'req_selected',
name: 'Leaf A',
parentId: subFolderA._id,
url: 'https://api.example.com/leaf-a',
method: 'GET',
});

// Folder B is unrelated and must not appear in the export.
const folderB = await services.requestGroup.create({
_id: 'fld_B',
name: 'Folder B',
parentId: workspace._id,
description: 'folder b docs that should not leak into a filtered export',
});

await services.request.create({
_id: 'req_unrelated',
name: 'Unrelated Request',
parentId: folderB._id,
url: 'https://api.example.com/unrelated',
method: 'GET',
});

const result = await getInsomniaV5DataExport({
workspaceId: workspace._id,
includePrivateEnvironments: false,
requestIds: [selectedRequest._id],
});

const parsed = YAML.parse(result);
expect(parsed.collection).toHaveLength(1);
expect(parsed.collection[0].name).toBe('Folder A');
expect(parsed.collection[0].children).toHaveLength(1);
expect(parsed.collection[0].children[0].name).toBe('Sub Folder A');
expect(parsed.collection[0].children[0].children).toHaveLength(1);
expect(parsed.collection[0].children[0].children[0].name).toBe('Leaf A');
// Ensure the serialized YAML does not carry over docs/names from unrelated folders.
expect(result).not.toContain('Folder B');
expect(result).not.toContain('folder b docs');
expect(result).not.toContain('Unrelated Request');
});

it('handles design workspace correctly', async () => {
const workspace = await services.workspace.create({
_id: 'wrk_design_test',
Expand Down
27 changes: 24 additions & 3 deletions packages/insomnia/src/common/insomnia-v5.ts
Original file line number Diff line number Diff line change
Expand Up @@ -832,6 +832,23 @@ export async function getInsomniaV5DataExport({
return false;
});

// When a requestIds filter is supplied, pre-compute the set of folders that
// are ancestors of at least one selected resource. Folders outside this set
// are excluded so users who pick a subset of requests don't end up with an
// export that drags in every unrelated folder and its docs.
const hasRequestFilter = Array.isArray(requestIds) && requestIds.length > 0;
const selectedAncestorGroupIds = new Set<string>();
if (hasRequestFilter) {
const resourcesById = new Map(workspaceDescendants.map(resource => [resource._id, resource]));
for (const id of requestIds!) {
let parent = resourcesById.get(resourcesById.get(id)?.parentId ?? '');
while (parent && models.requestGroup.isRequestGroup(parent) && !selectedAncestorGroupIds.has(parent._id)) {
selectedAncestorGroupIds.add(parent._id);
parent = resourcesById.get(parent.parentId);
}
}
}

/**
* Recursively builds a collection structure from flat resource list
* This function converts the flat list of resources into a hierarchical structure
Expand All @@ -850,12 +867,16 @@ export async function getInsomniaV5DataExport({
// Filter resources based on requestIds filter and parent relationship
resources
.filter(resource => {
// Include all request groups, or filter by requestIds if specified
if (!requestIds || requestIds.length === 0 || models.requestGroup.isRequestGroup(resource)) {
if (!hasRequestFilter) {
return true;
}

return requestIds.includes(resource._id);
// Only include folders that are ancestors of a selected request.
if (models.requestGroup.isRequestGroup(resource)) {
return selectedAncestorGroupIds.has(resource._id);
}

return requestIds!.includes(resource._id);
})
.filter(resource => resource.parentId === parentId)
.forEach(resource => {
Expand Down