Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add the ability to optionally update an existing collection on import #3615

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import Modal from 'components/Modal';
import Portal from 'components/Portal/index';

const ConfirmCollectionImportUpdate = ({ onConfirm, onCancel }) => {
return (
<Portal>
<Modal
size="md"
title="Update existing collection"
confirmText="Yes"
cancelText="No"
disableEscapeKey={true}
disableCloseOnOutsideClick={true}
handleConfirm={onConfirm}
handleCancel={onCancel}
hideClose={true}
>
<div>Would you like to add to the existing collection at this location?</div>
</Modal>
</Portal>
);
};

export default ConfirmCollectionImportUpdate;
56 changes: 50 additions & 6 deletions packages/bruno-app/src/components/Sidebar/TitleBar/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,16 @@ import { useDispatch } from 'react-redux';
import { showHomePage } from 'providers/ReduxStore/slices/app';
import { openCollection, importCollection } from 'providers/ReduxStore/slices/collections/actions';
import StyledWrapper from './StyledWrapper';
import ConfirmCollectionImportUpdate from 'components/ConfirmCollectionImportUpdate/index';

const TitleBar = () => {
const [importedCollection, setImportedCollection] = useState(null);
const [importedCollectionLocation, setImportedCollectionLocation] = useState(null);
const [importedTranslationLog, setImportedTranslationLog] = useState({});
const [createCollectionModalOpen, setCreateCollectionModalOpen] = useState(false);
const [importCollectionModalOpen, setImportCollectionModalOpen] = useState(false);
const [importCollectionLocationModalOpen, setImportCollectionLocationModalOpen] = useState(false);
const [confirmCollectionImportUpdateModalOpen, setConfirmCollectionImportUpdateModalOpen] = useState(false);
const dispatch = useDispatch();
const { ipcRenderer } = window;

Expand All @@ -30,20 +33,55 @@ const TitleBar = () => {
setImportCollectionLocationModalOpen(true);
};

const cleanupAfterSuccessfulImport = () => {
setImportCollectionLocationModalOpen(false);
setImportedCollection(null);
setImportedCollectionLocation(null);
toast.success('Collection imported successfully');
};

const cleanupAndShowImportError = (err) => {
setImportCollectionLocationModalOpen(false);
setImportedCollection(null);
setImportedCollectionLocation(null);
console.error(err);
toast.error('An error occurred while importing the collection. Check the logs for more information.');
};

const handleImportCollectionLocation = (collectionLocation) => {
setImportedCollectionLocation(collectionLocation);
setImportCollectionLocationModalOpen(false);
dispatch(importCollection(importedCollection, collectionLocation))
.then(() => {
setImportCollectionLocationModalOpen(false);
setImportedCollection(null);
toast.success('Collection imported successfully');
cleanupAfterSuccessfulImport();
})
.catch((err) => {
setImportCollectionLocationModalOpen(false);
console.error(err);
toast.error('An error occurred while importing the collection. Check the logs for more information.');
// Note: the string here must exactly match the start of the error thrown in
// `bruno-electron/src/ipc/collection.js` when the folder already exists
if (err instanceof Error && err.message.includes('collection already exists')) {
setConfirmCollectionImportUpdateModalOpen(true);
} else {
cleanupAndShowImportError(err);
}
});
};

const handleConfirmCollectionImportUpdate = (shouldUpdate) => {
setConfirmCollectionImportUpdateModalOpen(false);
setImportCollectionLocationModalOpen(false);
if (shouldUpdate) {
dispatch(importCollection(importedCollection, importedCollectionLocation, true))
.then(() => {
cleanupAfterSuccessfulImport();
})
.catch((err) => {
cleanupAndShowImportError(err);
});
} else {
cleanupAndShowImportError();
}
};

const menuDropdownTippyRef = useRef();
const onMenuDropdownCreate = (ref) => (menuDropdownTippyRef.current = ref);
const MenuIcon = forwardRef((props, ref) => {
Expand Down Expand Up @@ -80,6 +118,12 @@ const TitleBar = () => {
handleSubmit={handleImportCollectionLocation}
/>
) : null}
{confirmCollectionImportUpdateModalOpen ? (
<ConfirmCollectionImportUpdate
onConfirm={() => handleConfirmCollectionImportUpdate(true)}
onCancel={() => handleConfirmCollectionImportUpdate(false)}
/>
) : null}

<div className="flex items-center">
<button className="flex items-center gap-2 text-sm font-medium" onClick={handleTitleClick}>
Expand Down
56 changes: 50 additions & 6 deletions packages/bruno-app/src/components/Welcome/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,18 @@ import CreateCollection from 'components/Sidebar/CreateCollection';
import ImportCollection from 'components/Sidebar/ImportCollection';
import ImportCollectionLocation from 'components/Sidebar/ImportCollectionLocation';
import StyledWrapper from './StyledWrapper';
import ConfirmCollectionImportUpdate from 'components/ConfirmCollectionImportUpdate/index';

const Welcome = () => {
const dispatch = useDispatch();
const { t } = useTranslation();
const [importedCollection, setImportedCollection] = useState(null);
const [importedCollectionLocation, setImportedCollectionLocation] = useState(null);
const [importedTranslationLog, setImportedTranslationLog] = useState({});
const [createCollectionModalOpen, setCreateCollectionModalOpen] = useState(false);
const [importCollectionModalOpen, setImportCollectionModalOpen] = useState(false);
const [importCollectionLocationModalOpen, setImportCollectionLocationModalOpen] = useState(false);
const [confirmCollectionImportUpdateModalOpen, setConfirmCollectionImportUpdateModalOpen] = useState(false);

const handleOpenCollection = () => {
dispatch(openCollection()).catch((err) => console.log(err) && toast.error(t('WELCOME.COLLECTION_OPEN_ERROR')));
Expand All @@ -33,20 +36,55 @@ const Welcome = () => {
setImportCollectionLocationModalOpen(true);
};

const cleanupAfterSuccessfulImport = () => {
setImportCollectionLocationModalOpen(false);
setImportedCollection(null);
setImportedCollectionLocation(null);
toast.success(t('WELCOME.COLLECTION_IMPORT_SUCCESS'));
};

const cleanupAndShowImportError = (err) => {
setImportCollectionLocationModalOpen(false);
setImportedCollection(null);
setImportedCollectionLocation(null);
console.error(err);
toast.error(t('WELCOME.COLLECTION_IMPORT_ERROR'));
};

const handleImportCollectionLocation = (collectionLocation) => {
setImportedCollectionLocation(collectionLocation);
setImportCollectionLocationModalOpen(false);
dispatch(importCollection(importedCollection, collectionLocation))
.then(() => {
setImportCollectionLocationModalOpen(false);
setImportedCollection(null);
toast.success(t('WELCOME.COLLECTION_IMPORT_SUCCESS'));
cleanupAfterSuccessfulImport();
})
.catch((err) => {
setImportCollectionLocationModalOpen(false);
console.error(err);
toast.error(t('WELCOME.COLLECTION_IMPORT_ERROR'));
// Note: the string here must exactly match the start of the error thrown in
// `bruno-electron/src/ipc/collection.js` when the folder already exists
if (err instanceof Error && err.message.includes('collection already exists')) {
setConfirmCollectionImportUpdateModalOpen(true);
} else {
cleanupAndShowImportError(err);
}
});
};

const handleConfirmCollectionImportUpdate = (shouldUpdate) => {
setConfirmCollectionImportUpdateModalOpen(false);
setImportCollectionLocationModalOpen(false);
if (shouldUpdate) {
dispatch(importCollection(importedCollection, importedCollectionLocation, true))
.then(() => {
cleanupAfterSuccessfulImport();
})
.catch((err) => {
cleanupAndShowImportError(err);
});
} else {
cleanupAndShowImportError();
}
};

return (
<StyledWrapper className="pb-4 px-6 mt-6">
{createCollectionModalOpen ? <CreateCollection onClose={() => setCreateCollectionModalOpen(false)} /> : null}
Expand All @@ -61,6 +99,12 @@ const Welcome = () => {
handleSubmit={handleImportCollectionLocation}
/>
) : null}
{confirmCollectionImportUpdateModalOpen ? (
<ConfirmCollectionImportUpdate
onConfirm={() => handleConfirmCollectionImportUpdate(true)}
onCancel={() => handleConfirmCollectionImportUpdate(false)}
/>
) : null}

<div aria-hidden className="">
<Bruno width={50} />
Expand Down
Copy link
Author

@jGowgiel jGowgiel Dec 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Many of the changes in this file are just the result of running the existing formatter for this project.

Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ export const saveFolderRoot = (collectionUid, folderUid) => (dispatch, getState)

export const sendCollectionOauth2Request = (collectionUid, itemUid) => (dispatch, getState) => {
const state = getState();
const { globalEnvironments, activeGlobalEnvironmentUid } = state.globalEnvironments;
const { globalEnvironments, activeGlobalEnvironmentUid } = state.globalEnvironments;
const collection = findCollectionByUid(state.collections.collections, collectionUid);

return new Promise((resolve, reject) => {
Expand All @@ -196,7 +196,10 @@ export const sendCollectionOauth2Request = (collectionUid, itemUid) => (dispatch
let collectionCopy = cloneDeep(collection);

// add selected global env variables to the collection object
const globalEnvironmentVariables = getGlobalEnvironmentVariables({ globalEnvironments, activeGlobalEnvironmentUid });
const globalEnvironmentVariables = getGlobalEnvironmentVariables({
globalEnvironments,
activeGlobalEnvironmentUid
});
collectionCopy.globalEnvironmentVariables = globalEnvironmentVariables;

const environment = findEnvironmentInCollection(collectionCopy, collection.activeEnvironmentUid);
Expand All @@ -219,7 +222,7 @@ export const sendCollectionOauth2Request = (collectionUid, itemUid) => (dispatch

export const sendRequest = (item, collectionUid) => (dispatch, getState) => {
const state = getState();
const { globalEnvironments, activeGlobalEnvironmentUid } = state.globalEnvironments;
const { globalEnvironments, activeGlobalEnvironmentUid } = state.globalEnvironments;
const collection = findCollectionByUid(state.collections.collections, collectionUid);

return new Promise((resolve, reject) => {
Expand All @@ -231,7 +234,10 @@ export const sendRequest = (item, collectionUid) => (dispatch, getState) => {
let collectionCopy = cloneDeep(collection);

// add selected global env variables to the collection object
const globalEnvironmentVariables = getGlobalEnvironmentVariables({ globalEnvironments, activeGlobalEnvironmentUid });
const globalEnvironmentVariables = getGlobalEnvironmentVariables({
globalEnvironments,
activeGlobalEnvironmentUid
});
collectionCopy.globalEnvironmentVariables = globalEnvironmentVariables;

const environment = findEnvironmentInCollection(collectionCopy, collectionCopy.activeEnvironmentUid);
Expand Down Expand Up @@ -297,7 +303,7 @@ export const cancelRunnerExecution = (cancelTokenUid) => (dispatch) => {

export const runCollectionFolder = (collectionUid, folderUid, recursive, delay) => (dispatch, getState) => {
const state = getState();
const { globalEnvironments, activeGlobalEnvironmentUid } = state.globalEnvironments;
const { globalEnvironments, activeGlobalEnvironmentUid } = state.globalEnvironments;
const collection = findCollectionByUid(state.collections.collections, collectionUid);

return new Promise((resolve, reject) => {
Expand All @@ -308,7 +314,10 @@ export const runCollectionFolder = (collectionUid, folderUid, recursive, delay)
let collectionCopy = cloneDeep(collection);

// add selected global env variables to the collection object
const globalEnvironmentVariables = getGlobalEnvironmentVariables({ globalEnvironments, activeGlobalEnvironmentUid });
const globalEnvironmentVariables = getGlobalEnvironmentVariables({
globalEnvironments,
activeGlobalEnvironmentUid
});
collectionCopy.globalEnvironmentVariables = globalEnvironmentVariables;

const folder = findItemInCollection(collectionCopy, folderUid);
Expand Down Expand Up @@ -989,15 +998,16 @@ export const selectEnvironment = (environmentUid, collectionUid) => (dispatch, g

const collectionCopy = cloneDeep(collection);

const environmentName = environmentUid
? findEnvironmentInCollection(collectionCopy, environmentUid)?.name
: null;
const environmentName = environmentUid ? findEnvironmentInCollection(collectionCopy, environmentUid)?.name : null;

if (environmentUid && !environmentName) {
return reject(new Error('Environment not found'));
}

ipcRenderer.invoke('renderer:update-ui-state-snapshot', { type: 'COLLECTION_ENVIRONMENT', data: { collectionPath: collection?.pathname, environmentName }});
}

ipcRenderer.invoke('renderer:update-ui-state-snapshot', {
type: 'COLLECTION_ENVIRONMENT',
data: { collectionPath: collection?.pathname, environmentName }
});

dispatch(_selectEnvironment({ environmentUid, collectionUid }));
resolve();
Expand Down Expand Up @@ -1140,11 +1150,14 @@ export const collectionAddEnvFileEvent = (payload) => (dispatch, getState) => {
});
};

export const importCollection = (collection, collectionLocation) => (dispatch, getState) => {
export const importCollection = (collection, collectionLocation, updateExistingCollection) => (dispatch, getState) => {
Copy link
Author

@jGowgiel jGowgiel Dec 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method was materially updated, not just formatted (with a new argument).

return new Promise((resolve, reject) => {
const { ipcRenderer } = window;

ipcRenderer.invoke('renderer:import-collection', collection, collectionLocation).then(resolve).catch(reject);
ipcRenderer
.invoke('renderer:import-collection', collection, collectionLocation, updateExistingCollection)
.then(resolve)
.catch(reject);
});
};

Expand All @@ -1164,32 +1177,30 @@ export const saveCollectionSecurityConfig = (collectionUid, securityConfig) => (
});
};


export const hydrateCollectionWithUiStateSnapshot = (payload) => (dispatch, getState) => {
const collectionSnapshotData = payload;
return new Promise((resolve, reject) => {
const state = getState();
try {
if(!collectionSnapshotData) resolve();
const { pathname, selectedEnvironment } = collectionSnapshotData;
const collection = findCollectionByPathname(state.collections.collections, pathname);
const collectionCopy = cloneDeep(collection);
const collectionUid = collectionCopy?.uid;

// update selected environment
if (selectedEnvironment) {
const environment = findEnvironmentInCollectionByName(collectionCopy, selectedEnvironment);
if (environment) {
dispatch(_selectEnvironment({ environmentUid: environment?.uid, collectionUid }));
}
const collectionSnapshotData = payload;
return new Promise((resolve, reject) => {
const state = getState();
try {
if (!collectionSnapshotData) resolve();
const { pathname, selectedEnvironment } = collectionSnapshotData;
const collection = findCollectionByPathname(state.collections.collections, pathname);
const collectionCopy = cloneDeep(collection);
const collectionUid = collectionCopy?.uid;

// update selected environment
if (selectedEnvironment) {
const environment = findEnvironmentInCollectionByName(collectionCopy, selectedEnvironment);
if (environment) {
dispatch(_selectEnvironment({ environmentUid: environment?.uid, collectionUid }));
}

// todo: add any other redux state that you want to save

resolve();
}
catch(error) {
reject(error);
}
});
};

// todo: add any other redux state that you want to save

resolve();
} catch (error) {
reject(error);
}
});
};
Loading