mirror of
https://github.com/usebruno/bruno.git
synced 2024-11-24 17:03:47 +01:00
[Feature] : Bulk env import and UX/UI improvements (#2509)
* feat(bulk-env-import): bulk import working like a charm * feat(bulk-env-import): refresh no env dialog's styling * feat(bulk-env-import): group create and import env within initial modal, UI improvements * feat(bulk-env-import): minor styling fixes * feat(bulk-env-import): handle incorrect files in env importer --------- Co-authored-by: bpoulaindev <bpoulainpro@gmail.com>
This commit is contained in:
parent
01605f6f2a
commit
71353b0404
@ -1,13 +1,12 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import Portal from 'components/Portal';
|
||||
import Modal from 'components/Modal';
|
||||
import toast from 'react-hot-toast';
|
||||
import { useFormik } from 'formik';
|
||||
import { addEnvironment } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import * as Yup from 'yup';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { SharedButton } from 'components/Environments/EnvironmentSettings';
|
||||
|
||||
const CreateEnvironment = ({ collection, onClose }) => {
|
||||
const CreateEnvironment = ({ collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const inputRef = useRef();
|
||||
const formik = useFormik({
|
||||
@ -25,7 +24,6 @@ const CreateEnvironment = ({ collection, onClose }) => {
|
||||
dispatch(addEnvironment(values.name, collection.uid))
|
||||
.then(() => {
|
||||
toast.success('Environment created in collection');
|
||||
onClose();
|
||||
})
|
||||
.catch(() => toast.error('An error occurred while created the environment'));
|
||||
}
|
||||
@ -42,39 +40,32 @@ const CreateEnvironment = ({ collection, onClose }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
<Modal
|
||||
size="sm"
|
||||
title={'Create Environment'}
|
||||
confirmText="Create"
|
||||
handleConfirm={onSubmit}
|
||||
handleCancel={onClose}
|
||||
>
|
||||
<form className="bruno-form" onSubmit={formik.handleSubmit}>
|
||||
<div>
|
||||
<label htmlFor="name" className="block font-semibold">
|
||||
Environment Name
|
||||
</label>
|
||||
<input
|
||||
id="environment-name"
|
||||
type="text"
|
||||
name="name"
|
||||
ref={inputRef}
|
||||
className="block textbox mt-2 w-full"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
onChange={formik.handleChange}
|
||||
value={formik.values.name || ''}
|
||||
/>
|
||||
{formik.touched.name && formik.errors.name ? (
|
||||
<div className="text-red-500">{formik.errors.name}</div>
|
||||
) : null}
|
||||
</div>
|
||||
</form>
|
||||
</Modal>
|
||||
</Portal>
|
||||
<form className="bruno-form" onSubmit={formik.handleSubmit}>
|
||||
<div>
|
||||
<label htmlFor="name" className="block font-semibold">
|
||||
Environment Name
|
||||
</label>
|
||||
<div className="flex items-center mt-2">
|
||||
<input
|
||||
id="environment-name"
|
||||
type="text"
|
||||
name="name"
|
||||
ref={inputRef}
|
||||
className="block textbox w-full"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
onChange={formik.handleChange}
|
||||
value={formik.values.name || ''}
|
||||
/>
|
||||
<SharedButton className="py-2.5 ml-1" onClick={onSubmit}>
|
||||
Create
|
||||
</SharedButton>
|
||||
</div>
|
||||
{formik.touched.name && formik.errors.name ? <div className="text-red-500">{formik.errors.name}</div> : null}
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,38 +1,46 @@
|
||||
import React from 'react';
|
||||
import Portal from 'components/Portal';
|
||||
import toast from 'react-hot-toast';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import importPostmanEnvironment from 'utils/importers/postman-environment';
|
||||
import { importEnvironment } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { toastError } from 'utils/common/error';
|
||||
import Modal from 'components/Modal';
|
||||
import { IconDatabaseImport } from '@tabler/icons';
|
||||
|
||||
const ImportEnvironment = ({ onClose, collection }) => {
|
||||
const ImportEnvironment = ({ collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleImportPostmanEnvironment = () => {
|
||||
importPostmanEnvironment()
|
||||
.then((environment) => {
|
||||
dispatch(importEnvironment(environment.name, environment.variables, collection.uid))
|
||||
.then(() => {
|
||||
toast.success('Environment imported successfully');
|
||||
onClose();
|
||||
})
|
||||
.catch(() => toast.error('An error occurred while importing the environment'));
|
||||
.then((environments) => {
|
||||
environments
|
||||
.filter((env) =>
|
||||
env.name && env.name !== 'undefined'
|
||||
? true
|
||||
: () => {
|
||||
toast.error('Failed to import environment: env has no name');
|
||||
return false;
|
||||
}
|
||||
)
|
||||
.map((environment) => {
|
||||
dispatch(importEnvironment(environment.name, environment.variables, collection.uid))
|
||||
.then(() => {
|
||||
toast.success('Environment imported successfully');
|
||||
})
|
||||
.catch(() => toast.error('An error occurred while importing the environment'));
|
||||
});
|
||||
})
|
||||
.catch((err) => toastError(err, 'Postman Import environment failed'));
|
||||
};
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
<Modal size="sm" title="Import Environment" hideFooter={true} handleConfirm={onClose} handleCancel={onClose}>
|
||||
<div>
|
||||
<div className="text-link hover:underline cursor-pointer" onClick={handleImportPostmanEnvironment}>
|
||||
Postman Environment
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</Portal>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleImportPostmanEnvironment}
|
||||
className="flex justify-center flex-col items-center w-full dark:bg-zinc-700 rounded-lg border-2 border-dashed border-zinc-300 dark:border-zinc-400 p-12 text-center hover:border-zinc-400 focus:outline-none focus:ring-2 focus:ring-amber-500 focus:ring-offset-2"
|
||||
>
|
||||
<IconDatabaseImport size={64} />
|
||||
<span className="mt-2 block text-sm font-semibold">Import your Postman environments</span>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -4,6 +4,43 @@ import CreateEnvironment from './CreateEnvironment';
|
||||
import EnvironmentList from './EnvironmentList';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import ImportEnvironment from './ImportEnvironment';
|
||||
import { IconFileAlert } from '@tabler/icons';
|
||||
|
||||
export const SharedButton = ({ children, className, onClick }) => {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
className={`rounded bg-transparent px-2.5 py-2 w-fit text-xs font-semibold text-zinc-900 dark:text-zinc-50 shadow-sm ring-1 ring-inset ring-zinc-300 dark:ring-zinc-500 hover:bg-gray-50 dark:hover:bg-zinc-700
|
||||
${className}`}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
const DefaultTab = ({ setTab }) => {
|
||||
return (
|
||||
<div className="text-center items-center flex flex-col">
|
||||
<IconFileAlert size={64} strokeWidth={1} />
|
||||
<span className="font-semibold mt-2">No environments found</span>
|
||||
<span className="font-extralight mt-2 text-zinc-500 dark:text-zinc-400">
|
||||
Get started by using the following buttons :
|
||||
</span>
|
||||
<div className="flex items-center justify-center mt-6">
|
||||
<SharedButton onClick={() => setTab('create')}>
|
||||
<span>Create Environment</span>
|
||||
</SharedButton>
|
||||
|
||||
<span className="mx-4">Or</span>
|
||||
|
||||
<SharedButton onClick={() => setTab('import')}>
|
||||
<span>Import Environment</span>
|
||||
</SharedButton>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const EnvironmentSettings = ({ collection, onClose }) => {
|
||||
const [isModified, setIsModified] = useState(false);
|
||||
@ -11,38 +48,25 @@ const EnvironmentSettings = ({ collection, onClose }) => {
|
||||
const [openCreateModal, setOpenCreateModal] = useState(false);
|
||||
const [openImportModal, setOpenImportModal] = useState(false);
|
||||
const [selectedEnvironment, setSelectedEnvironment] = useState(null);
|
||||
|
||||
const [tab, setTab] = useState('default');
|
||||
if (!environments || !environments.length) {
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<Modal
|
||||
size="md"
|
||||
title="Environments"
|
||||
confirmText={'Close'}
|
||||
handleConfirm={onClose}
|
||||
confirmText={'Go back'}
|
||||
handleConfirm={() => setTab('default')}
|
||||
handleCancel={onClose}
|
||||
hideCancel={true}
|
||||
>
|
||||
{openCreateModal && <CreateEnvironment collection={collection} onClose={() => setOpenCreateModal(false)} />}
|
||||
{openImportModal && <ImportEnvironment collection={collection} onClose={() => setOpenImportModal(false)} />}
|
||||
<div className="text-center flex flex-col">
|
||||
<p>No environments found!</p>
|
||||
<button
|
||||
className="btn-create-environment text-link pr-2 py-3 mt-2 select-none"
|
||||
onClick={() => setOpenCreateModal(true)}
|
||||
>
|
||||
<span>Create Environment</span>
|
||||
</button>
|
||||
|
||||
<span>Or</span>
|
||||
|
||||
<button
|
||||
className="btn-import-environment text-link pl-2 pr-2 py-3 select-none"
|
||||
onClick={() => setOpenImportModal(true)}
|
||||
>
|
||||
<span>Import Environment</span>
|
||||
</button>
|
||||
</div>
|
||||
{tab === 'create' ? (
|
||||
<CreateEnvironment collection={collection} />
|
||||
) : tab === 'import' ? (
|
||||
<ImportEnvironment collection={collection} />
|
||||
) : (
|
||||
<DefaultTab setTab={setTab} />
|
||||
)}
|
||||
</Modal>
|
||||
</StyledWrapper>
|
||||
);
|
||||
|
@ -60,7 +60,7 @@ const ImportCollection = ({ onClose, handleSubmit }) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
className={`rounded bg-transparent px-2.5 py-1 text-xs font-semibold text-slate-900 dark:text-slate-50 shadow-sm ring-1 ring-inset ring-zinc-300 dark:ring-zinc-500 hover:bg-gray-50 dark:hover:bg-zinc-700
|
||||
className={`rounded bg-transparent px-2.5 py-1 text-xs font-semibold text-zinc-900 dark:text-zinc-50 shadow-sm ring-1 ring-inset ring-zinc-300 dark:ring-zinc-500 hover:bg-gray-50 dark:hover:bg-zinc-700
|
||||
${className}`}
|
||||
>
|
||||
{children}
|
||||
|
@ -57,10 +57,20 @@ const parsePostmanEnvironment = (str) => {
|
||||
|
||||
const importEnvironment = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
fileDialog({ accept: 'application/json' })
|
||||
.then(readFile)
|
||||
.then(parsePostmanEnvironment)
|
||||
.then((environment) => resolve(environment))
|
||||
fileDialog({ multiple: true, accept: 'application/json' })
|
||||
.then((files) => {
|
||||
return Promise.all(
|
||||
Object.values(files ?? {}).map((file) =>
|
||||
readFile([file])
|
||||
.then(parsePostmanEnvironment)
|
||||
.catch((err) => {
|
||||
console.error(`Error processing file: ${file.name || 'undefined'}`, err);
|
||||
throw err;
|
||||
})
|
||||
)
|
||||
);
|
||||
})
|
||||
.then((environments) => resolve(environments))
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
reject(new BrunoError('Import Environment failed'));
|
||||
|
Loading…
Reference in New Issue
Block a user