From 03dcb6b7b9a67279f487ed7e3fef172ed6e4d238 Mon Sep 17 00:00:00 2001 From: Abhishek S Lal Date: Thu, 26 Mar 2026 20:16:14 +0530 Subject: [PATCH] feat: implement item sorting for Postman export (#7581) - Added functions to sort items by sequence and name, ensuring folders are prioritized over requests in the export output. - Enhanced the `brunoToPostman` function to utilize the new sorting logic. - Introduced comprehensive tests to validate the sorting behavior for folders and requests, including nested structures. --- .../src/postman/bruno-to-postman.js | 49 +++++++- .../tests/postman/bruno-to-postman.spec.js | 110 ++++++++++++++++++ 2 files changed, 158 insertions(+), 1 deletion(-) diff --git a/packages/bruno-converters/src/postman/bruno-to-postman.js b/packages/bruno-converters/src/postman/bruno-to-postman.js index d865783fd..c543efb46 100644 --- a/packages/bruno-converters/src/postman/bruno-to-postman.js +++ b/packages/bruno-converters/src/postman/bruno-to-postman.js @@ -2,6 +2,51 @@ import map from 'lodash/map'; import { deleteSecretsInEnvs, deleteUidsInEnvs, deleteUidsInItems, isItemARequest } from '../common'; import translateBruToPostman from '../utils/bruno-to-postman-translator'; +const isItemAFolder = (item) => item.type === 'folder'; + +const sortItemsBySequence = (items) => [...items].sort((a, b) => a.seq - b.seq); + +const sortByNameThenSequence = (items) => { + const isSeqValid = (seq) => Number.isFinite(seq) && Number.isInteger(seq) && seq > 0; + + const alphabeticallySorted = [...items].sort((a, b) => a.name && b.name && a.name.localeCompare(b.name)); + + const withoutSeq = alphabeticallySorted.filter((f) => !isSeqValid(f['seq'])); + const withSeq = alphabeticallySorted.filter((f) => isSeqValid(f['seq'])).sort((a, b) => a.seq - b.seq); + + const sortedItems = withoutSeq; + + withSeq.forEach((item) => { + const position = item.seq - 1; + const existingItem = withoutSeq[position]; + + const hasItemWithSameSeq = Array.isArray(existingItem) + ? existingItem?.[0]?.seq === item.seq + : existingItem?.seq === item.seq; + + if (hasItemWithSameSeq) { + const newGroup = Array.isArray(existingItem) ? [...existingItem, item] : [existingItem, item]; + withoutSeq.splice(position, 1, newGroup); + } else { + withoutSeq.splice(position, 0, item); + } + }); + + return sortedItems.flat(); +}; + +const sortItemsForExport = (items) => { + if (!items || !Array.isArray(items)) return []; + + const folders = items.filter((item) => item && isItemAFolder(item)); + const requests = items.filter((item) => item && isItemARequest(item)); + + const sortedFolders = sortByNameThenSequence(folders); + const sortedRequests = sortItemsBySequence(requests); + + return [...sortedFolders, ...sortedRequests]; +}; + /** * Transforms a given URL string into an object representing the protocol, host, path, query, and variables. * @@ -497,7 +542,9 @@ export const brunoToPostman = (collection) => { return []; } - return map(itemsArray, (item) => { + const sortedItems = sortItemsForExport(itemsArray); + + return map(sortedItems, (item) => { if (!item) { return null; } diff --git a/packages/bruno-converters/tests/postman/bruno-to-postman.spec.js b/packages/bruno-converters/tests/postman/bruno-to-postman.spec.js index 542ea2686..c2b372d38 100644 --- a/packages/bruno-converters/tests/postman/bruno-to-postman.spec.js +++ b/packages/bruno-converters/tests/postman/bruno-to-postman.spec.js @@ -937,3 +937,113 @@ describe('brunoToPostman event handling', () => { expect(nestedRequest.event[0].script.exec).toEqual(['console.log("nested pre");']); }); }); + +describe('brunoToPostman item ordering', () => { + const makeRequest = (name, seq) => ({ + type: 'http-request', + name, + seq, + request: { + method: 'GET', + url: 'https://example.com', + headers: [], + params: [], + body: { mode: 'none' }, + auth: { mode: 'none' } + } + }); + + const makeFolder = (name, seq, items = []) => ({ + type: 'folder', + name, + seq, + items + }); + + it('should place folders before requests in export output', () => { + const collection = { + items: [ + makeRequest('Request A', 1), + makeFolder('Folder B'), + makeRequest('Request C', 2), + makeFolder('Folder A') + ] + }; + + const result = brunoToPostman(collection); + const names = result.item.map((i) => i.name); + + // Folders first (alphabetical since no seq), then requests (by seq) + expect(names[0]).toBe('Folder A'); + expect(names[1]).toBe('Folder B'); + expect(names[2]).toBe('Request A'); + expect(names[3]).toBe('Request C'); + }); + + it('should sort requests by seq ascending', () => { + const collection = { + items: [ + makeRequest('Third', 3), + makeRequest('First', 1), + makeRequest('Second', 2) + ] + }; + + const result = brunoToPostman(collection); + const names = result.item.map((i) => i.name); + + expect(names).toEqual(['First', 'Second', 'Third']); + }); + + it('should sort folders by name then sequence', () => { + const collection = { + items: [ + makeFolder('Gamma', undefined), + makeFolder('Alpha', undefined), + makeFolder('Beta', 1) + ] + }; + + const result = brunoToPostman(collection); + const names = result.item.map((i) => i.name); + + // Beta has seq=1, so it goes to position 0; Alpha and Gamma are alphabetical + expect(names[0]).toBe('Beta'); + expect(names[1]).toBe('Alpha'); + expect(names[2]).toBe('Gamma'); + }); + + it('should sort items recursively within nested folders', () => { + const collection = { + items: [ + makeFolder('Parent', 1, [ + makeRequest('Nested C', 3), + makeFolder('Nested Folder', 1), + makeRequest('Nested A', 1) + ]) + ] + }; + + const result = brunoToPostman(collection); + const parent = result.item[0]; + const nestedNames = parent.item.map((i) => i.name); + + // Folder first, then requests sorted by seq + expect(nestedNames).toEqual(['Nested Folder', 'Nested A', 'Nested C']); + }); + + it('should handle folders without seq (older collections) alphabetically', () => { + const collection = { + items: [ + makeFolder('Zebra', undefined), + makeFolder('Apple', undefined), + makeFolder('Mango', undefined) + ] + }; + + const result = brunoToPostman(collection); + const names = result.item.map((i) => i.name); + + expect(names).toEqual(['Apple', 'Mango', 'Zebra']); + }); +});