feat(import): hide File Format option and default collections to Open… (#8247)

This commit is contained in:
ravindra-bruno
2026-06-17 16:56:33 +05:30
committed by GitHub
parent ba063f6d82
commit 7b94e069e9
3 changed files with 233 additions and 133 deletions

View File

@@ -13,10 +13,13 @@ const Wrapper = styled.div`
.current-group:hover {
background-color: ${(props) => darken(0.03, props.theme.background.surface1)};
border-color: ${(props) => darken(0.03, props.theme.background.surface2)};
}
/* Fix dropdown positioning */
[data-tippy-root] {
left: 0 !important;
.advanced-options {
.caret {
color: ${(props) => props.theme.textLink};
fill: ${(props) => props.theme.textLink};
}
}
`;

View File

@@ -15,6 +15,7 @@ import { wsdlToBruno } from '@usebruno/converters';
import { toastError } from 'utils/common/error';
import { addLog } from 'providers/ReduxStore/slices/logs';
import { useBetaFeature, BETA_FEATURES } from 'utils/beta-features';
import Portal from 'components/Portal';
import Modal from 'components/Modal';
import Help from 'components/Help';
import Dropdown from 'components/Dropdown';
@@ -109,9 +110,11 @@ const ImportCollectionLocation = ({ onClose, handleSubmit, rawData, format, sour
const dispatch = useDispatch();
const [groupingType, setGroupingType] = useState('tags');
const [collectionFormat, setCollectionFormat] = useState(DEFAULT_COLLECTION_FORMAT);
const [showFileFormat, setShowFileFormat] = useState(false);
const isOpenAPISyncEnabled = useBetaFeature(BETA_FEATURES.OPENAPI_SYNC);
const [enableCheckForSpecUpdates, setEnableCheckForSpecUpdates] = useState(isOpenAPISyncEnabled);
const dropdownTippyRef = useRef();
const optionsDropdownTippyRef = useRef();
const isOpenApi = format === 'openapi';
const isZipImport = format === 'bruno-zip';
const isOpenApiFromUrl = isOpenApi && !!sourceUrl && !filePath;
@@ -198,6 +201,21 @@ const ImportCollectionLocation = ({ onClose, handleSubmit, rawData, format, sour
dropdownTippyRef.current = ref;
};
const onOptionsDropdownCreate = (ref) => {
optionsDropdownTippyRef.current = ref;
};
const ImportOptions = forwardRef((props, ref) => {
return (
<div ref={ref} className="flex items-center text-link cursor-pointer">
<button className="btn-advanced" type="button">
Options
</button>
<IconCaretDown className="caret ml-1" size={14} strokeWidth={2} />
</div>
);
});
const GroupingDropdownIcon = forwardRef((props, ref) => {
const selectedOption = groupingOptions.find((option) => option.value === groupingType);
return (
@@ -243,140 +261,160 @@ const ImportCollectionLocation = ({ onClose, handleSubmit, rawData, format, sour
};
return (
<StyledWrapper>
<Modal
size="md"
title="Import Collection"
confirmText="Import"
handleConfirm={onSubmit}
handleCancel={onClose}
dataTestId="import-collection-location-modal"
>
<form className="bruno-form" onSubmit={(e) => e.preventDefault()}>
<div>
<label htmlFor="collectionName" className="block font-medium">
Name
</label>
<div className="mt-2">{collectionName}</div>
<>
<label htmlFor="collectionLocation" className="font-medium mt-4 flex items-center">
Location
<Help>
<p>Bruno stores your collections on your computer's filesystem.</p>
<p className="mt-2">Choose the location where you want to store this collection.</p>
</Help>
</label>
<input
id="collection-location"
type="text"
name="collectionLocation"
className="block textbox mt-2 w-full cursor-pointer"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
value={formik.values.collectionLocation || ''}
onClick={browse}
onChange={(e) => {
formik.setFieldValue('collectionLocation', e.target.value);
}}
/>
</>
{formik.touched.collectionLocation && formik.errors.collectionLocation ? (
<div className="text-red-500">{formik.errors.collectionLocation}</div>
) : null}
<div className="mt-1">
<span
data-testid="import-collection-browse-link"
className="text-link cursor-pointer hover:underline"
onClick={browse}
>
Browse
</span>
</div>
{!isZipImport && (
<div className="mt-4">
<label htmlFor="format" className="flex items-center font-medium">
File Format
<Help width="300">
<p>Choose the file format for storing requests in this collection.</p>
<p className="mt-2">
<strong>OpenCollection (YAML):</strong> Industry-standard YAML format (.yml files)
</p>
<p className="mt-1">
<strong>BRU:</strong> Bruno's native file format (.bru files)
</p>
</Help>
</label>
<select
id="format"
name="format"
className="block textbox mt-2 w-full"
value={collectionFormat}
onChange={(e) => setCollectionFormat(e.target.value)}
>
<option value="yml">OpenCollection (YAML)</option>
<option value="bru">BRU Format (.bru)</option>
</select>
</div>
)}
</div>
{isOpenApi && (
<div className="mt-4 flex gap-4 items-center justify-between">
<div>
<label htmlFor="groupingType" className="block font-medium">
Folder arrangement
</label>
<p className="text-muted text-xs mt-1 mb-2">
Select whether to create folders according to the spec's paths or tags.
</p>
</div>
<div className="relative">
<Dropdown onCreate={onDropdownCreate} icon={<GroupingDropdownIcon />} placement="bottom-start">
{groupingOptions.map((option) => (
<div
key={option.value}
className="dropdown-item"
data-testid={option.testId}
onClick={() => {
dropdownTippyRef?.current?.hide();
setGroupingType(option.value);
}}
>
{option.label}
</div>
))}
<Portal>
<StyledWrapper>
<Modal
size="md"
title="Import Collection"
confirmText="Import"
handleConfirm={onSubmit}
handleCancel={onClose}
dataTestId="import-collection-location-modal"
footerLeft={
!isZipImport ? (
<div className="advanced-options flex">
<Dropdown onCreate={onOptionsDropdownCreate} icon={<ImportOptions />} placement="bottom-start">
<div
className="dropdown-item"
data-testid="show-file-format-toggle"
onClick={() => {
optionsDropdownTippyRef?.current?.hide();
setShowFileFormat(!showFileFormat);
}}
>
{showFileFormat ? 'Hide File Format' : 'Show File Format'}
</div>
</Dropdown>
</div>
</div>
)}
{showCheckForSpecUpdatesOption && (
<div className={`mt-4 ${isSwagger2 ? 'opacity-50 pointer-events-none' : ''}`}>
<label className={`flex items-center gap-2 ${isSwagger2 ? '' : 'cursor-pointer'}`}>
<input
type="checkbox"
checked={isSwagger2 ? false : enableCheckForSpecUpdates}
onChange={(e) => setEnableCheckForSpecUpdates(e.target.checked)}
disabled={isSwagger2}
className={`checkbox ${isSwagger2 ? '' : 'cursor-pointer'}`}
/>
<span className="font-medium">Check for Spec Updates</span>
) : null
}
>
<form className="bruno-form" onSubmit={(e) => e.preventDefault()}>
<div>
<label htmlFor="collectionName" className="block font-medium">
Name
</label>
<p className="text-muted text-xs mt-1">
{isSwagger2
? 'OpenAPI Sync is not supported for Swagger 2.0 specs.'
: 'Stay notified of spec changes and sync your collection with the spec.'}
</p>
<div className="mt-2">{collectionName}</div>
<>
<label htmlFor="collectionLocation" className="font-medium mt-4 flex items-center">
Location
<Help>
<p>Bruno stores your collections on your computer's filesystem.</p>
<p className="mt-2">Choose the location where you want to store this collection.</p>
</Help>
</label>
<input
id="collection-location"
type="text"
name="collectionLocation"
className="block textbox mt-2 w-full cursor-pointer"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
value={formik.values.collectionLocation || ''}
onClick={browse}
onChange={(e) => {
formik.setFieldValue('collectionLocation', e.target.value);
}}
/>
</>
{formik.touched.collectionLocation && formik.errors.collectionLocation ? (
<div className="text-red-500">{formik.errors.collectionLocation}</div>
) : null}
<div className="mt-1">
<span
data-testid="import-collection-browse-link"
className="text-link cursor-pointer hover:underline"
onClick={browse}
>
Browse
</span>
</div>
{showFileFormat && !isZipImport && (
<div className="mt-4">
<label htmlFor="format" className="flex items-center font-medium">
File Format
<Help width="300">
<p>Choose the file format for storing requests in this collection.</p>
<p className="mt-2">
<strong>OpenCollection (YAML):</strong> Industry-standard YAML format (.yml files)
</p>
<p className="mt-1">
<strong>BRU:</strong> Bruno's native file format (.bru files)
</p>
</Help>
</label>
<select
id="format"
name="format"
className="block textbox mt-2 w-full"
value={collectionFormat}
onChange={(e) => setCollectionFormat(e.target.value)}
>
<option value="yml">OpenCollection (YAML)</option>
<option value="bru">BRU Format (.bru)</option>
</select>
</div>
)}
</div>
)}
</form>
</Modal>
</StyledWrapper>
{isOpenApi && (
<div className="mt-4 flex gap-4 items-center justify-between">
<div>
<label htmlFor="groupingType" className="block font-medium">
Folder arrangement
</label>
<p className="text-muted text-xs mt-1 mb-2">
Select whether to create folders according to the spec's paths or tags.
</p>
</div>
<div className="relative">
<Dropdown onCreate={onDropdownCreate} icon={<GroupingDropdownIcon />} placement="bottom-start">
{groupingOptions.map((option) => (
<div
key={option.value}
className="dropdown-item"
data-testid={option.testId}
onClick={() => {
dropdownTippyRef?.current?.hide();
setGroupingType(option.value);
}}
>
{option.label}
</div>
))}
</Dropdown>
</div>
</div>
)}
{showCheckForSpecUpdatesOption && (
<div className={`mt-4 ${isSwagger2 ? 'opacity-50 pointer-events-none' : ''}`}>
<label className={`flex items-center gap-2 ${isSwagger2 ? '' : 'cursor-pointer'}`}>
<input
type="checkbox"
checked={isSwagger2 ? false : enableCheckForSpecUpdates}
onChange={(e) => setEnableCheckForSpecUpdates(e.target.checked)}
disabled={isSwagger2}
className={`checkbox ${isSwagger2 ? '' : 'cursor-pointer'}`}
/>
<span className="font-medium">Check for Spec Updates</span>
</label>
<p className="text-muted text-xs mt-1">
{isSwagger2
? 'OpenAPI Sync is not supported for Swagger 2.0 specs.'
: 'Stay notified of spec changes and sync your collection with the spec.'}
</p>
</div>
)}
</form>
</Modal>
</StyledWrapper>
</Portal>
);
};

View File

@@ -0,0 +1,59 @@
import { test, expect } from '../../../playwright';
import * as path from 'path';
import { closeAllCollections } from '../../utils/page';
import { buildCommonLocators } from '../../utils/page/locators';
test.describe('Import Collection File Format Options toggle', () => {
test.afterAll(async ({ page }) => {
await closeAllCollections(page);
});
test('hides File Format by default and reveals it via the Options toggle', async ({ page }) => {
const postmanFile = path.resolve(__dirname, 'fixtures', 'postman-import-apikey-header-collection.json');
const locators = buildCommonLocators(page);
await test.step('Open import collection modal', async () => {
await locators.plusMenu.button().click();
await locators.plusMenu.importCollection().click();
await expect(locators.modal.title('Import Collection')).toBeVisible();
});
await test.step('Upload Postman collection and reach the location modal', async () => {
await locators.import.fileInput().setInputFiles(postmanFile);
await locators.import.locationModal().waitFor({ state: 'visible', timeout: 10000 });
});
const locationModal = locators.import.locationModal();
await test.step('File Format selector is hidden by default', async () => {
await expect(locationModal.locator('#format')).toHaveCount(0);
});
await test.step('Options toggle reveals the File Format selector defaulting to OpenCollection (YAML)', async () => {
await locationModal.getByRole('button', { name: 'Options' }).click();
await page.getByTestId('show-file-format-toggle').click();
const formatSelect = locationModal.locator('#format');
await expect(formatSelect).toBeVisible();
});
await test.step('OpenCollection (YAML) is pre-selected and BRU is available', async () => {
const formatSelect = locationModal.locator('#format');
await expect(formatSelect.locator('option')).toHaveText(['OpenCollection (YAML)', 'BRU Format (.bru)']);
await expect(formatSelect).toHaveValue('yml');
});
await test.step('Toggle can switch the format and hide the selector again', async () => {
const formatSelect = locationModal.locator('#format');
await formatSelect.selectOption('bru');
await expect(formatSelect).toHaveValue('bru');
await locationModal.getByRole('button', { name: 'Options' }).click();
const toggle = page.getByTestId('show-file-format-toggle');
await expect(toggle).toHaveText('Hide File Format');
await toggle.click();
await expect(locationModal.locator('#format')).toHaveCount(0);
});
});
});