Merge branch 'usebruno:main' into feature/1602-multipart-content-type

This commit is contained in:
busy-panda🐼🐼 2024-05-01 09:19:08 +02:00 committed by GitHub
commit 9ba03a5f02
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 141 additions and 48 deletions

View File

@ -0,0 +1,42 @@
import React from 'react';
import { IconAlertTriangle } from '@tabler/icons';
import Modal from 'components/Modal';
import { createPortal } from 'react-dom';
const ConfirmSwitchEnv = ({ onCancel }) => {
return createPortal(
<Modal
size="md"
title="Unsaved changes"
confirmText="Save and Close"
cancelText="Close without saving"
disableEscapeKey={true}
disableCloseOnOutsideClick={true}
closeModalFadeTimeout={150}
handleCancel={onCancel}
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
}}
hideFooter={true}
>
<div className="flex items-center font-normal">
<IconAlertTriangle size={32} strokeWidth={1.5} className="text-yellow-600" />
<h1 className="ml-2 text-lg font-semibold">Hold on..</h1>
</div>
<div className="font-normal mt-4">You have unsaved changes in this environment.</div>
<div className="flex justify-between mt-6">
<div>
<button className="btn btn-sm btn-danger" onClick={onCancel}>
Close
</button>
</div>
<div></div>
</div>
</Modal>,
document.body
);
};
export default ConfirmSwitchEnv;

View File

@ -1,19 +1,19 @@
import React from 'react'; import React from 'react';
import toast from 'react-hot-toast';
import cloneDeep from 'lodash/cloneDeep';
import { IconTrash } from '@tabler/icons'; import { IconTrash } from '@tabler/icons';
import { useTheme } from 'providers/Theme'; import { useTheme } from 'providers/Theme';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { saveEnvironment } from 'providers/ReduxStore/slices/collections/actions';
import SingleLineEditor from 'components/SingleLineEditor'; import SingleLineEditor from 'components/SingleLineEditor';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
import { uuid } from 'utils/common';
import { maskInputValue } from 'utils/collections';
import { useFormik } from 'formik'; import { useFormik } from 'formik';
import * as Yup from 'yup'; import * as Yup from 'yup';
import { uuid } from 'utils/common';
import { variableNameRegex } from 'utils/common/regex'; import { variableNameRegex } from 'utils/common/regex';
import { maskInputValue } from 'utils/collections'; import { saveEnvironment } from 'providers/ReduxStore/slices/collections/actions';
import cloneDeep from 'lodash/cloneDeep';
import toast from 'react-hot-toast';
const EnvironmentVariables = ({ environment, collection }) => { const EnvironmentVariables = ({ environment, collection, setIsModified, originalEnvironmentVariables }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const { storedTheme } = useTheme(); const { storedTheme } = useTheme();
@ -46,11 +46,17 @@ const EnvironmentVariables = ({ environment, collection }) => {
.then(() => { .then(() => {
toast.success('Changes saved successfully'); toast.success('Changes saved successfully');
formik.resetForm({ values }); formik.resetForm({ values });
setIsModified(false);
}) })
.catch(() => toast.error('An error occurred while saving the changes')); .catch(() => toast.error('An error occurred while saving the changes'));
} }
}); });
// Effect to track modifications.
React.useEffect(() => {
setIsModified(formik.dirty);
}, [formik.dirty]);
const ErrorMessage = ({ name }) => { const ErrorMessage = ({ name }) => {
const meta = formik.getFieldMeta(name); const meta = formik.getFieldMeta(name);
if (!meta.error) { if (!meta.error) {
@ -80,6 +86,10 @@ const EnvironmentVariables = ({ environment, collection }) => {
formik.setValues(formik.values.filter((variable) => variable.uid !== id)); formik.setValues(formik.values.filter((variable) => variable.uid !== id));
}; };
const handleReset = () => {
formik.resetForm({ originalEnvironmentVariables });
};
return ( return (
<StyledWrapper className="w-full mt-6 mb-6"> <StyledWrapper className="w-full mt-6 mb-6">
<div className="h-[50vh] overflow-y-auto w-full"> <div className="h-[50vh] overflow-y-auto w-full">
@ -162,6 +172,9 @@ const EnvironmentVariables = ({ environment, collection }) => {
<button type="submit" className="submit btn btn-md btn-secondary mt-2" onClick={formik.handleSubmit}> <button type="submit" className="submit btn btn-md btn-secondary mt-2" onClick={formik.handleSubmit}>
Save Save
</button> </button>
<button type="submit" className="ml-2 px-1 submit btn btn-md btn-secondary mt-2" onClick={handleReset}>
Reset
</button>
</div> </div>
</StyledWrapper> </StyledWrapper>
); );

View File

@ -5,7 +5,7 @@ import DeleteEnvironment from '../../DeleteEnvironment';
import RenameEnvironment from '../../RenameEnvironment'; import RenameEnvironment from '../../RenameEnvironment';
import EnvironmentVariables from './EnvironmentVariables'; import EnvironmentVariables from './EnvironmentVariables';
const EnvironmentDetails = ({ environment, collection }) => { const EnvironmentDetails = ({ environment, collection, setIsModified }) => {
const [openEditModal, setOpenEditModal] = useState(false); const [openEditModal, setOpenEditModal] = useState(false);
const [openDeleteModal, setOpenDeleteModal] = useState(false); const [openDeleteModal, setOpenDeleteModal] = useState(false);
const [openCopyModal, setOpenCopyModal] = useState(false); const [openCopyModal, setOpenCopyModal] = useState(false);
@ -38,7 +38,7 @@ const EnvironmentDetails = ({ environment, collection }) => {
</div> </div>
<div> <div>
<EnvironmentVariables key={environment.uid} environment={environment} collection={collection} /> <EnvironmentVariables environment={environment} collection={collection} setIsModified={setIsModified} />
</div> </div>
</div> </div>
); );

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState, forwardRef, useRef } from 'react'; import React, { useEffect, useState } from 'react';
import { findEnvironmentInCollection } from 'utils/collections'; import { findEnvironmentInCollection } from 'utils/collections';
import usePrevious from 'hooks/usePrevious'; import usePrevious from 'hooks/usePrevious';
import EnvironmentDetails from './EnvironmentDetails'; import EnvironmentDetails from './EnvironmentDetails';
@ -7,19 +7,23 @@ import { IconDownload, IconShieldLock } from '@tabler/icons';
import ImportEnvironment from '../ImportEnvironment'; import ImportEnvironment from '../ImportEnvironment';
import ManageSecrets from '../ManageSecrets'; import ManageSecrets from '../ManageSecrets';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
import ConfirmSwitchEnv from './ConfirmSwitchEnv';
const EnvironmentList = ({ collection }) => { const EnvironmentList = ({ selectedEnvironment, setSelectedEnvironment, collection, isModified, setIsModified }) => {
const { environments } = collection; const { environments } = collection;
const [selectedEnvironment, setSelectedEnvironment] = useState(null);
const [openCreateModal, setOpenCreateModal] = useState(false); const [openCreateModal, setOpenCreateModal] = useState(false);
const [openImportModal, setOpenImportModal] = useState(false); const [openImportModal, setOpenImportModal] = useState(false);
const [openManageSecretsModal, setOpenManageSecretsModal] = useState(false); const [openManageSecretsModal, setOpenManageSecretsModal] = useState(false);
const [switchEnvConfirmClose, setSwitchEnvConfirmClose] = useState(false);
const [originalEnvironmentVariables, setOriginalEnvironmentVariables] = useState([]);
const envUids = environments ? environments.map((env) => env.uid) : []; const envUids = environments ? environments.map((env) => env.uid) : [];
const prevEnvUids = usePrevious(envUids); const prevEnvUids = usePrevious(envUids);
useEffect(() => { useEffect(() => {
if (selectedEnvironment) { if (selectedEnvironment) {
setOriginalEnvironmentVariables(selectedEnvironment.variables);
return; return;
} }
@ -32,7 +36,6 @@ const EnvironmentList = ({ collection }) => {
}, [collection, environments, selectedEnvironment]); }, [collection, environments, selectedEnvironment]);
useEffect(() => { useEffect(() => {
// check env add
if (prevEnvUids && prevEnvUids.length && envUids.length > prevEnvUids.length) { if (prevEnvUids && prevEnvUids.length && envUids.length > prevEnvUids.length) {
const newEnv = environments.find((env) => !prevEnvUids.includes(env.uid)); const newEnv = environments.find((env) => !prevEnvUids.includes(env.uid));
if (newEnv) { if (newEnv) {
@ -40,23 +43,62 @@ const EnvironmentList = ({ collection }) => {
} }
} }
// check env delete
if (prevEnvUids && prevEnvUids.length && envUids.length < prevEnvUids.length) { if (prevEnvUids && prevEnvUids.length && envUids.length < prevEnvUids.length) {
setSelectedEnvironment(environments && environments.length ? environments[0] : null); setSelectedEnvironment(environments && environments.length ? environments[0] : null);
} }
}, [envUids, environments, prevEnvUids]); }, [envUids, environments, prevEnvUids]);
const handleEnvironmentClick = (env) => {
if (!isModified) {
setSelectedEnvironment(env);
} else {
setSwitchEnvConfirmClose(true);
}
};
if (!selectedEnvironment) { if (!selectedEnvironment) {
return null; return null;
} }
const handleCreateEnvClick = () => {
if (!isModified) {
setOpenCreateModal(true);
} else {
setSwitchEnvConfirmClose(true);
}
};
const handleImportClick = () => {
if (!isModified) {
setOpenImportModal(true);
} else {
setSwitchEnvConfirmClose(true);
}
};
const handleSecretsClick = () => {
setOpenManageSecretsModal(true);
};
const handleConfirmSwitch = (saveChanges) => {
if (!saveChanges) {
setSwitchEnvConfirmClose(false);
}
};
return ( return (
<StyledWrapper> <StyledWrapper>
{openCreateModal && <CreateEnvironment collection={collection} onClose={() => setOpenCreateModal(false)} />} {openCreateModal && <CreateEnvironment collection={collection} onClose={() => setOpenCreateModal(false)} />}
{openImportModal && <ImportEnvironment collection={collection} onClose={() => setOpenImportModal(false)} />} {openImportModal && <ImportEnvironment collection={collection} onClose={() => setOpenImportModal(false)} />}
{openManageSecretsModal && <ManageSecrets onClose={() => setOpenManageSecretsModal(false)} />} {openManageSecretsModal && <ManageSecrets onClose={() => setOpenManageSecretsModal(false)} />}
<div className="flex"> <div className="flex">
<div> <div>
{switchEnvConfirmClose && (
<div className="flex items-center justify-between tab-container px-1">
<ConfirmSwitchEnv onCancel={() => handleConfirmSwitch(false)} />
</div>
)}
<div className="environments-sidebar flex flex-col"> <div className="environments-sidebar flex flex-col">
{environments && {environments &&
environments.length && environments.length &&
@ -64,28 +106,33 @@ const EnvironmentList = ({ collection }) => {
<div <div
key={env.uid} key={env.uid}
className={selectedEnvironment.uid === env.uid ? 'environment-item active' : 'environment-item'} className={selectedEnvironment.uid === env.uid ? 'environment-item active' : 'environment-item'}
onClick={() => setSelectedEnvironment(env)} onClick={() => handleEnvironmentClick(env)} // Use handleEnvironmentClick to handle clicks
> >
<span className="break-all">{env.name}</span> <span className="break-all">{env.name}</span>
</div> </div>
))} ))}
<div className="btn-create-environment" onClick={() => setOpenCreateModal(true)}> <div className="btn-create-environment" onClick={() => handleCreateEnvClick()}>
+ <span>Create</span> + <span>Create</span>
</div> </div>
<div className="mt-auto btn-import-environment"> <div className="mt-auto btn-import-environment">
<div className="flex items-center" onClick={() => setOpenImportModal(true)}> <div className="flex items-center" onClick={() => handleImportClick()}>
<IconDownload size={12} strokeWidth={2} /> <IconDownload size={12} strokeWidth={2} />
<span className="label ml-1 text-xs">Import</span> <span className="label ml-1 text-xs">Import</span>
</div> </div>
<div className="flex items-center mt-2" onClick={() => setOpenManageSecretsModal(true)}> <div className="flex items-center mt-2" onClick={() => handleSecretsClick()}>
<IconShieldLock size={12} strokeWidth={2} /> <IconShieldLock size={12} strokeWidth={2} />
<span className="label ml-1 text-xs">Managing Secrets</span> <span className="label ml-1 text-xs">Managing Secrets</span>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<EnvironmentDetails environment={selectedEnvironment} collection={collection} /> <EnvironmentDetails
environment={selectedEnvironment}
collection={collection}
setIsModified={setIsModified}
originalEnvironmentVariables={originalEnvironmentVariables}
/>
</div> </div>
</StyledWrapper> </StyledWrapper>
); );

View File

@ -6,9 +6,11 @@ import StyledWrapper from './StyledWrapper';
import ImportEnvironment from './ImportEnvironment'; import ImportEnvironment from './ImportEnvironment';
const EnvironmentSettings = ({ collection, onClose }) => { const EnvironmentSettings = ({ collection, onClose }) => {
const [isModified, setIsModified] = useState(false);
const { environments } = collection; const { environments } = collection;
const [openCreateModal, setOpenCreateModal] = useState(false); const [openCreateModal, setOpenCreateModal] = useState(false);
const [openImportModal, setOpenImportModal] = useState(false); const [openImportModal, setOpenImportModal] = useState(false);
const [selectedEnvironment, setSelectedEnvironment] = useState(null);
if (!environments || !environments.length) { if (!environments || !environments.length) {
return ( return (
@ -48,7 +50,13 @@ const EnvironmentSettings = ({ collection, onClose }) => {
return ( return (
<Modal size="lg" title="Environments" handleCancel={onClose} hideFooter={true}> <Modal size="lg" title="Environments" handleCancel={onClose} hideFooter={true}>
<EnvironmentList collection={collection} /> <EnvironmentList
selectedEnvironment={selectedEnvironment}
setSelectedEnvironment={setSelectedEnvironment}
collection={collection}
isModified={isModified}
setIsModified={setIsModified}
/>
</Modal> </Modal>
); );
}; };

View File

@ -41,7 +41,9 @@ const Auth = ({ item, collection }) => {
<div>Collection level auth is: </div> <div>Collection level auth is: </div>
<div className="inherit-mode-text">{humanizeRequestAuthMode(collectionAuth?.mode)}</div> <div className="inherit-mode-text">{humanizeRequestAuthMode(collectionAuth?.mode)}</div>
</div> </div>
<div className="text-sm opacity-50">Cannot inherit Oauth2 from collection.</div> <div className="text-sm opacity-50">
Note: You need to use scripting to set the access token in the request headers.
</div>
</div> </div>
) : ( ) : (
<> <>

View File

@ -129,7 +129,7 @@ const Sidebar = () => {
Star Star
</GitHubButton> */} </GitHubButton> */}
</div> </div>
<div className="flex flex-grow items-center justify-end text-xs mr-2">v1.14.0</div> <div className="flex flex-grow items-center justify-end text-xs mr-2">v1.16.1</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -60,7 +60,7 @@ const trackStart = () => {
event: 'start', event: 'start',
properties: { properties: {
os: platformLib.os.family, os: platformLib.os.family,
version: '1.14.0' version: '1.16.1'
} }
}); });
}; };

View File

@ -417,7 +417,10 @@ export const transformRequestToSaveToFilesystem = (item) => {
}); });
if (itemToSave.request.body.mode === 'json') { if (itemToSave.request.body.mode === 'json') {
itemToSave.request.body.json = replaceTabsWithSpaces(itemToSave.request.body.json); itemToSave.request.body = {
...itemToSave.request.body,
json: replaceTabsWithSpaces(itemToSave.request.body.json)
};
} }
return itemToSave; return itemToSave;

View File

@ -63,19 +63,8 @@ const prepareRequest = (request, collectionRoot) => {
headers: headers headers: headers
}; };
/**
* 27 Feb 2024:
* ['inherit', 'none'].includes(request.auth.mode)
* We are mainitaining the old behavior where 'none' used to inherit the collection auth.
*
* Very soon, 'none' will be treated as no auth and 'inherit' will be the only way to inherit collection auth.
* We will request users to update their collection files to use 'inherit' instead of 'none'.
* Don't want to break ongoing CI pipelines.
*
* Hoping to remove this by 1 April 2024.
*/
const collectionAuth = get(collectionRoot, 'request.auth'); const collectionAuth = get(collectionRoot, 'request.auth');
if (collectionAuth && ['inherit', 'none'].includes(request.auth.mode)) { if (collectionAuth && request.auth.mode === 'none') {
if (collectionAuth.mode === 'basic') { if (collectionAuth.mode === 'basic') {
axiosRequest.auth = { axiosRequest.auth = {
username: get(collectionAuth, 'basic.username'), username: get(collectionAuth, 'basic.username'),

View File

@ -1,5 +1,5 @@
{ {
"version": "v1.14.0", "version": "v1.16.1",
"name": "bruno", "name": "bruno",
"description": "Opensource API Client for Exploring and Testing APIs", "description": "Opensource API Client for Exploring and Testing APIs",
"homepage": "https://www.usebruno.com", "homepage": "https://www.usebruno.com",

View File

@ -34,20 +34,9 @@ const parseFormData = (datas, collectionPath) => {
return form; return form;
}; };
/**
* 27 Feb 2024:
* ['inherit', 'none'].includes(request.auth.mode)
* We are mainitaining the old behavior where 'none' used to inherit the collection auth.
*
* Very soon, 'none' will be treated as no auth and 'inherit' will be the only way to inherit collection auth.
* We will request users to update their collection files to use 'inherit' instead of 'none'.
* Don't want to break ongoing CI pipelines.
*
* Hoping to remove this by 1 April 2024.
*/
const setAuthHeaders = (axiosRequest, request, collectionRoot) => { const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
const collectionAuth = get(collectionRoot, 'request.auth'); const collectionAuth = get(collectionRoot, 'request.auth');
if (collectionAuth && ['inherit', 'none'].includes(request.auth.mode)) { if (collectionAuth && request.auth.mode === 'inherit') {
switch (collectionAuth.mode) { switch (collectionAuth.mode) {
case 'awsv4': case 'awsv4':
axiosRequest.awsv4config = { axiosRequest.awsv4config = {