Merge pull request #5557 from Pragadesh-45/feat/default-location

feat: default location for collections
This commit is contained in:
Pragadesh-45
2025-09-25 22:53:08 +05:30
committed by GitHub
parent 187f5ca011
commit 123fe7d542
15 changed files with 199 additions and 8 deletions

View File

@@ -3,6 +3,7 @@ import get from 'lodash/get';
import { useFormik } from 'formik';
import { useSelector, useDispatch } from 'react-redux';
import { savePreferences } from 'providers/ReduxStore/slices/app';
import { browseDirectory } from 'providers/ReduxStore/slices/collections/actions';
import StyledWrapper from './StyledWrapper';
import * as Yup from 'yup';
import toast from 'react-hot-toast';
@@ -35,7 +36,8 @@ const General = ({ close }) => {
})
.test('isValidTimeout', 'Request Timeout must be equal or greater than 0', (value) => {
return value === undefined || Number(value) >= 0;
})
}),
defaultCollectionLocation: Yup.string().max(1024)
});
const formik = useFormik({
@@ -50,7 +52,8 @@ const General = ({ close }) => {
},
timeout: preferences.request.timeout,
storeCookies: get(preferences, 'request.storeCookies', true),
sendCookies: get(preferences, 'request.sendCookies', true)
sendCookies: get(preferences, 'request.sendCookies', true),
defaultCollectionLocation: get(preferences, 'general.defaultCollectionLocation', '')
},
validationSchema: preferencesSchema,
onSubmit: async (values) => {
@@ -79,6 +82,9 @@ const General = ({ close }) => {
timeout: newPreferences.timeout,
storeCookies: newPreferences.storeCookies,
sendCookies: newPreferences.sendCookies
},
general: {
defaultCollectionLocation: newPreferences.defaultCollectionLocation
}
}))
.then(() => {
@@ -99,6 +105,19 @@ const General = ({ close }) => {
formik.setFieldValue('customCaCertificate.filePath', null);
};
const browseDefaultLocation = () => {
dispatch(browseDirectory())
.then((dirPath) => {
if (typeof dirPath === 'string') {
formik.setFieldValue('defaultCollectionLocation', dirPath);
}
})
.catch((error) => {
formik.setFieldValue('defaultCollectionLocation', '');
console.error(error);
});
};
return (
<StyledWrapper>
<form className="bruno-form" onSubmit={formik.handleSubmit}>
@@ -231,6 +250,35 @@ const General = ({ close }) => {
{formik.touched.timeout && formik.errors.timeout ? (
<div className="text-red-500">{formik.errors.timeout}</div>
) : null}
<div className="flex flex-col mt-6">
<label className="block select-none default-collection-location-label" htmlFor="defaultCollectionLocation">
Default Collection Location
</label>
<input
type="text"
name="defaultCollectionLocation"
className="block textbox mt-2 w-full cursor-pointer default-collection-location-input"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={formik.handleChange}
value={formik.values.defaultCollectionLocation || ''}
onClick={browseDefaultLocation}
placeholder="Click to browse for default location"
/>
<div className="mt-1">
<span
className="text-link cursor-pointer hover:underline default-collection-location-browse"
onClick={browseDefaultLocation}
>
Browse
</span>
</div>
</div>
{formik.touched.defaultCollectionLocation && formik.errors.defaultCollectionLocation ? (
<div className="text-red-500">{formik.errors.defaultCollectionLocation}</div>
) : null}
<div className="mt-10">
<button type="submit" className="submit btn btn-sm btn-secondary">
Save

View File

@@ -12,12 +12,15 @@ import PathDisplay from 'components/PathDisplay';
import { useState } from 'react';
import { IconArrowBackUp, IconEdit } from "@tabler/icons";
import { findCollectionByUid } from 'utils/collections/index';
import get from 'lodash/get';
const CloneCollection = ({ onClose, collectionUid }) => {
const inputRef = useRef();
const dispatch = useDispatch();
const [isEditing, toggleEditing] = useState(false);
const collection = useSelector(state => findCollectionByUid(state.collections.collections, collectionUid));
const preferences = useSelector((state) => state.app.preferences);
const defaultLocation = get(preferences, 'general.defaultCollectionLocation', '');
const { name } = collection;
const formik = useFormik({
@@ -25,7 +28,7 @@ const CloneCollection = ({ onClose, collectionUid }) => {
initialValues: {
collectionName: `${name} copy`,
collectionFolderName: `${sanitizeName(name)} copy`,
collectionLocation: ''
collectionLocation: defaultLocation
},
validationSchema: Yup.object({
collectionName: Yup.string()

View File

@@ -252,7 +252,7 @@ const Collection = ({ collection, searchText }) => {
</div>
{isLoading ? <IconLoader2 className="animate-spin mx-1" size={18} strokeWidth={1.5} /> : null}
</div>
<div className="collection-actions">
<div className="collection-actions" data-testid="collection-actions">
<Dropdown onCreate={onMenuDropdownCreate} icon={<MenuIcon />} placement="bottom-start">
<div
className="dropdown-item"
@@ -274,6 +274,7 @@ const Collection = ({ collection, searchText }) => {
</div>
<div
className="dropdown-item"
data-testid="clone-collection"
onClick={(_e) => {
menuDropdownTippyRef.current.hide();
setShowCloneCollectionModalOpen(true);

View File

@@ -1,5 +1,5 @@
import React, { useRef, useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import { useFormik } from 'formik';
import * as Yup from 'yup';
import { browseDirectory } from 'providers/ReduxStore/slices/collections/actions';
@@ -14,18 +14,21 @@ import Help from 'components/Help';
import { multiLineMsg } from "utils/common";
import { formatIpcError } from "utils/common/error";
import { toggleSidebarCollapse } from 'providers/ReduxStore/slices/app';
import get from 'lodash/get';
const CreateCollection = ({ onClose }) => {
const inputRef = useRef();
const dispatch = useDispatch();
const [isEditing, toggleEditing] = useState(false);
const preferences = useSelector((state) => state.app.preferences);
const defaultLocation = get(preferences, 'general.defaultCollectionLocation', '');
const formik = useFormik({
enableReinitialize: true,
initialValues: {
collectionName: '',
collectionFolderName: '',
collectionLocation: ''
collectionLocation: defaultLocation
},
validationSchema: Yup.object({
collectionName: Yup.string()

View File

@@ -82,7 +82,7 @@ const StatusBar = () => {
<ToolHint text="Preferences" toolhintId="Preferences" place="top-start" offset={10}>
<button
className="status-bar-button"
className="status-bar-button preferences-button"
data-trigger="preferences"
onClick={() => dispatch(showPreferences(true))}
tabIndex={0}

View File

@@ -78,7 +78,7 @@ const Welcome = () => {
aria-label={t('WELCOME.CREATE_COLLECTION')}
>
<IconPlus aria-hidden size={18} strokeWidth={2} />
<span className="label ml-2" id="create-collection">
<span className="label ml-2" id="create-collection" data-testid="create-collection">
{t('WELCOME.CREATE_COLLECTION')}
</span>
</button>

View File

@@ -25,6 +25,9 @@ const initialState = {
font: {
codeFont: 'default'
},
general: {
defaultCollectionLocation: ''
},
beta: {
grpc: false
}

View File

@@ -47,6 +47,9 @@ const defaultPreferences = {
},
onboarding: {
hasLaunchedBefore: false
},
general: {
defaultCollectionLocation: ''
}
};
@@ -89,6 +92,9 @@ const preferencesSchema = Yup.object().shape({
}),
onboarding: Yup.object({
hasLaunchedBefore: Yup.boolean()
}),
general: Yup.object({
defaultCollectionLocation: Yup.string().max(1024).nullable()
})
});

View File

@@ -0,0 +1,5 @@
{
"version": "1",
"name": "collection",
"type": "collection"
}

View File

@@ -0,0 +1,5 @@
meta {
name: collection
type: collection
version: 1.0.0
}

View File

@@ -0,0 +1,3 @@
vars {
host: https://www.httpfaker.org
}

View File

@@ -0,0 +1,11 @@
meta {
name: request
type: http
seq: 1
}
post {
url: {{host}}/api/echo
body: text
auth: none
}

View File

@@ -0,0 +1,84 @@
import { test, expect } from '../../../playwright';
test.describe('Default Collection Location Feature', () => {
test('Should hydrate the default location from preferences', async ({ pageWithUserData: page }) => {
// open preferences
await page.locator('.preferences-button').click();
// verify the default location is pre-filled
const defaultLocationInput = page.locator('.default-collection-location-input');
await expect(defaultLocationInput).toHaveValue('/tmp/bruno-collections');
// close the preferences
await page.locator('[data-test-id="modal-close-button"]').click();
// wait for 2 seconds
await page.waitForTimeout(2000);
});
test('Should save empty default location', async ({ pageWithUserData: page }) => {
// open preferences
await page.locator('.preferences-button').click();
// clear the default location field
const defaultLocationInput = page.locator('.default-collection-location-input');
await defaultLocationInput.clear();
// save preferences
await page.getByRole('button', { name: 'Save' }).click();
// verify success message
await expect(page.locator('text=Preferences saved successfully')).toBeVisible();
// wait for 2 seconds
await page.waitForTimeout(2000);
});
test('Should save a valid default location', async ({ pageWithUserData: page }) => {
// open preferences
await page.locator('.preferences-button').click();
// set a default location
const defaultLocationInput = page.locator('.default-collection-location-input');
// fill the default location input
await defaultLocationInput.fill('/tmp/bruno-collections');
// save preferences
await page.getByRole('button', { name: 'Save' }).click();
// verify success message
await expect(page.locator('text=Preferences saved successfully')).toBeVisible();
// wait for 2 seconds
await page.waitForTimeout(2000);
});
test('Should use default location in Create Collection modal', async ({ pageWithUserData: page }) => {
// test Create Collection modal
await page.locator('[data-testid="create-collection"]').click();
// verify the default location is pre-filled
const collectionLocationInput = page.getByLabel('Location');
await expect(collectionLocationInput).toHaveValue('/tmp/bruno-collections');
// cancel the collection creation
await page.getByRole('button', { name: 'Cancel' }).click();
// wait for 2 seconds
await page.waitForTimeout(2000);
});
test('Should use default location in Clone Collection modal', async ({ pageWithUserData: page }) => {
// open the clone collection modal
await page.locator('[data-testid="collection-actions"]').click();
await page.getByTestId('clone-collection').click();
// verify the default location is pre-filled
const cloneLocationInput = page.getByLabel('Location');
await expect(cloneLocationInput).toHaveValue('/tmp/bruno-collections');
// wait for 2 seconds
await page.waitForTimeout(2000);
});
});

View File

@@ -0,0 +1,10 @@
{
"collections": [
{
"path": "{{projectRoot}}/tests/preferences/default-collection-location/collection",
"securityConfig": {
"jsSandboxMode": "developer"
}
}
]
}

View File

@@ -0,0 +1,9 @@
{
"maximized": true,
"lastOpenedCollections": ["{{projectRoot}}/tests/preferences/default-collection-location/collection"],
"preferences": {
"general": {
"defaultCollectionLocation": "/tmp/bruno-collections"
}
}
}