-
Notifications
You must be signed in to change notification settings - Fork 2.6k
feat(app): add "Ignore Folder" sidebar context menu item (closes #1890) #8398
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,12 +19,14 @@ import { | |
| IconSettings, | ||
| IconInfoCircle, | ||
| IconTerminal2, | ||
| IconAppWindow | ||
| IconAppWindow, | ||
| IconEyeOff | ||
| } from '@tabler/icons'; | ||
| import { useSelector, useDispatch } from 'react-redux'; | ||
| import { addTab, focusTab, makeTabPermanent } from 'providers/ReduxStore/slices/tabs'; | ||
| import { handleCollectionItemDrop, sendRequest, showInFolder, pasteItem, saveRequest } from 'providers/ReduxStore/slices/collections/actions'; | ||
| import { toggleCollectionItem, addResponseExample } from 'providers/ReduxStore/slices/collections'; | ||
| import { handleCollectionItemDrop, sendRequest, showInFolder, pasteItem, saveRequest, updateBrunoConfig } from 'providers/ReduxStore/slices/collections/actions'; | ||
| import { toggleCollectionItem, addResponseExample, deleteItem } from 'providers/ReduxStore/slices/collections'; | ||
| import { getCollectionRelativePath, addPathToIgnoreList } from 'utils/collections/ignore'; | ||
| import { insertTaskIntoQueue } from 'providers/ReduxStore/slices/app'; | ||
| import { uuid } from 'utils/common'; | ||
| import { copyRequest, setFocusedSidebarPath } from 'providers/ReduxStore/slices/app'; | ||
|
|
@@ -337,6 +339,21 @@ const CollectionItem = ({ item, collectionUid, collectionPathname, searchText }) | |
|
|
||
| let indents = range(item.depth); | ||
|
|
||
| const handleIgnoreFolder = () => { | ||
| const relativePath = getCollectionRelativePath(collectionPathname, item.pathname); | ||
| if (!relativePath) return; | ||
|
|
||
| const brunoConfig = addPathToIgnoreList(collection?.brunoConfig, relativePath); | ||
| dispatch(updateBrunoConfig(brunoConfig, collectionUid)) | ||
| .then(() => { | ||
| // Remove the folder from the sidebar immediately; bruno.json keeps it | ||
| // ignored on subsequent loads. | ||
| dispatch(deleteItem({ itemUid: item.uid, collectionUid })); | ||
| toast.success(`Ignored folder: ${relativePath}`); | ||
|
Comment on lines
+347
to
+352
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🔴 Critical | 🏗️ Heavy lift Don't use Line 351 dispatches the same thunk used by the Delete action, and that thunk invokes 🤖 Prompt for AI Agents |
||
| }) | ||
| .catch(() => toast.error('Failed to ignore folder')); | ||
| }; | ||
|
|
||
| // Build menu items for MenuDropdown | ||
| const buildMenuItems = () => { | ||
| const items = []; | ||
|
|
@@ -465,6 +482,12 @@ const CollectionItem = ({ item, collectionUid, collectionPathname, searchText }) | |
| const folderCwd = item.pathname || collectionPathname; | ||
| await openDevtoolsAndSwitchToTerminal(dispatch, folderCwd); | ||
| } | ||
| }, | ||
| { | ||
| id: 'ignore-folder', | ||
| leftSection: IconEyeOff, | ||
| label: 'Ignore Folder', | ||
| onClick: handleIgnoreFolder | ||
| } | ||
| ); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| /** | ||
| * Returns the path of an item relative to its collection root, using forward | ||
| * slashes so the value stays portable when written into bruno.json (which can | ||
| * be committed and shared across operating systems). | ||
| * | ||
| * @param {string} collectionPathname absolute path of the collection root | ||
| * @param {string} itemPathname absolute path of the item (folder) being ignored | ||
| * @returns {string} the collection-relative path, or '' when it cannot be derived | ||
| */ | ||
| export const getCollectionRelativePath = (collectionPathname, itemPathname) => { | ||
| if (!collectionPathname || !itemPathname) return ''; | ||
|
|
||
| const toPosix = (p) => p.replace(/\\/g, '/').replace(/\/+$/, ''); | ||
| const root = toPosix(collectionPathname); | ||
| const target = toPosix(itemPathname); | ||
|
|
||
| if (target === root) return ''; | ||
| if (target.startsWith(`${root}/`)) { | ||
| return target.slice(root.length + 1); | ||
| } | ||
|
|
||
| return target; | ||
| }; | ||
|
Comment on lines
+10
to
+23
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win Return Line 22 currently falls back to the normalized absolute 🤖 Prompt for AI AgentsSource: Path instructions |
||
|
|
||
| /** | ||
| * Returns a new brunoConfig with the given path added to its `ignore` list. | ||
| * The original config and its ignore array are left untouched. Empty paths and | ||
| * paths already present are no-ops. | ||
| * | ||
| * @param {Object} brunoConfig the collection's bruno.json config | ||
| * @param {string} relativePath the collection-relative path to ignore | ||
| * @returns {Object} a new brunoConfig with the updated ignore list | ||
| */ | ||
| export const addPathToIgnoreList = (brunoConfig, relativePath) => { | ||
| const config = brunoConfig ? { ...brunoConfig } : {}; | ||
| const ignore = Array.isArray(config.ignore) ? [...config.ignore] : []; | ||
|
|
||
| if (relativePath && !ignore.includes(relativePath)) { | ||
| ignore.push(relativePath); | ||
| } | ||
|
|
||
| config.ignore = ignore; | ||
| return config; | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| import { getCollectionRelativePath, addPathToIgnoreList } from './ignore'; | ||
|
|
||
| describe('getCollectionRelativePath', () => { | ||
| it('returns the folder path relative to the collection root', () => { | ||
| expect(getCollectionRelativePath('/home/adi/my-collection', '/home/adi/my-collection/auth')).toBe('auth'); | ||
| }); | ||
|
|
||
| it('returns nested folder paths with forward slashes', () => { | ||
| expect(getCollectionRelativePath('/home/adi/my-collection', '/home/adi/my-collection/v1/users')).toBe('v1/users'); | ||
| }); | ||
|
|
||
| it('normalizes windows-style separators to forward slashes', () => { | ||
| expect(getCollectionRelativePath('C:\\Users\\adi\\coll', 'C:\\Users\\adi\\coll\\auth\\login')).toBe('auth/login'); | ||
| }); | ||
|
|
||
| it('tolerates a trailing slash on the collection path', () => { | ||
| expect(getCollectionRelativePath('/home/adi/coll/', '/home/adi/coll/auth')).toBe('auth'); | ||
| }); | ||
|
|
||
| it('returns an empty string when the item is the collection root', () => { | ||
| expect(getCollectionRelativePath('/home/adi/coll', '/home/adi/coll')).toBe(''); | ||
| }); | ||
|
|
||
| it('returns an empty string when inputs are missing', () => { | ||
| expect(getCollectionRelativePath('', '/home/adi/coll/auth')).toBe(''); | ||
| expect(getCollectionRelativePath('/home/adi/coll', '')).toBe(''); | ||
| }); | ||
| }); | ||
|
Comment on lines
+3
to
+28
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win Add a regression for non-descendant and case-mismatch inputs. The helper is documented to return 🤖 Prompt for AI AgentsSources: Coding guidelines, Path instructions |
||
|
|
||
| describe('addPathToIgnoreList', () => { | ||
| it('appends the path to an existing ignore list', () => { | ||
| const config = { name: 'coll', ignore: ['node_modules'] }; | ||
| expect(addPathToIgnoreList(config, 'auth').ignore).toEqual(['node_modules', 'auth']); | ||
| }); | ||
|
|
||
| it('creates the ignore list when the config has none', () => { | ||
| expect(addPathToIgnoreList({ name: 'coll' }, 'auth').ignore).toEqual(['auth']); | ||
| }); | ||
|
|
||
| it('does not add duplicates', () => { | ||
| const config = { ignore: ['auth'] }; | ||
| expect(addPathToIgnoreList(config, 'auth').ignore).toEqual(['auth']); | ||
| }); | ||
|
|
||
| it('ignores empty paths', () => { | ||
| const config = { ignore: ['auth'] }; | ||
| expect(addPathToIgnoreList(config, '').ignore).toEqual(['auth']); | ||
| }); | ||
|
|
||
| it('does not mutate the original config or its ignore array', () => { | ||
| const config = { ignore: ['node_modules'] }; | ||
| const next = addPathToIgnoreList(config, 'auth'); | ||
| expect(config.ignore).toEqual(['node_modules']); | ||
| expect(next).not.toBe(config); | ||
| expect(next.ignore).not.toBe(config.ignore); | ||
| }); | ||
| }); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
This merge starts from stale
brunoConfig.Line 346 reads
collection?.brunoConfigfrom Redux, butupdateBrunoConfig()only persists over IPC and does not update that state slice. Ignoring folder B right after folder A in the same session will rebuild from the pre-change config and can drop A frombruno.json.🤖 Prompt for AI Agents