mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-22 20:25:38 +00:00
feat(import): hide File Format option and default collections to Open… (#8247)
This commit is contained in:
@@ -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};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
59
tests/import/postman/import-file-format-options.spec.ts
Normal file
59
tests/import/postman/import-file-format-options.spec.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user