Skip to content

Commit 48c88df

Browse files
authored
fix: handle transient requests during app quit flow in SaveRequestsModal (usebruno#8003)
* fix: handle transient requests during app quit flow in SaveRequestsModal * test: non serial * chore: fix theme * fix: ui polish * chore: import * chore: cr
1 parent bdc5d1e commit 48c88df

5 files changed

Lines changed: 114 additions & 13 deletions

File tree

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import styled from 'styled-components';
2+
3+
const StyledWrapper = styled.div`
4+
padding-top: 0.5rem;
5+
padding-bottom: 0.5rem;
6+
padding-left: 0.75rem;
7+
padding-right: 0.75rem;
8+
background: ${({ theme }) => theme.background.crust};
9+
border: 1px solid ${({ theme }) => theme.border.border0};
10+
border-radius: ${({ theme }) => theme.border.radius.sm};
11+
12+
.request-name {
13+
color: ${({ theme }) => theme.text};
14+
}
15+
16+
.collection-name{
17+
color: ${({ theme }) => theme.colors.text.subtext1};
18+
}
19+
`;
20+
21+
export default StyledWrapper;

packages/bruno-app/src/components/SaveTransientRequest/Container.js renamed to packages/bruno-app/src/components/SaveTransientRequest/Container/index.js

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import { closeTabs } from 'providers/ReduxStore/slices/collections/actions';
77
import toast from 'react-hot-toast';
88
import Modal from 'components/Modal';
99
import Button from 'ui/Button';
10-
import SaveTransientRequest from './index';
10+
import SaveTransientRequest from 'components/SaveTransientRequest';
11+
import StyledWrapper from './StyledWrapper';
1112

1213
const SaveTransientRequestContainer = () => {
1314
const dispatch = useDispatch();
@@ -86,13 +87,13 @@ const SaveTransientRequestContainer = () => {
8687
{modals.map((modal) => {
8788
const { item, collection } = modal;
8889
return (
89-
<div
90+
<StyledWrapper
9091
key={item.uid}
91-
className="flex items-center justify-between py-2 px-3 bg-gray-50 rounded border border-gray-200"
92+
className="flex items-center justify-between"
9293
>
9394
<div className="flex flex-col flex-1 min-w-0 mr-3">
94-
<span className="text-sm text-gray-700 truncate">{item.name}</span>
95-
<span className="text-xs text-gray-500 truncate">
95+
<span className="text-sm request-name truncate">{item.name}</span>
96+
<span className="text-xs collection-name truncate">
9697
{collection.name}
9798
</span>
9899
</div>
@@ -105,13 +106,13 @@ const SaveTransientRequestContainer = () => {
105106
>
106107
Save
107108
</Button>
108-
</div>
109+
</StyledWrapper>
109110
);
110111
})}
111112
</div>
112113
</div>
113114

114-
<div className="flex justify-end mt-6 pt-4 border-t">
115+
<div className="flex justify-end mt-6 pt-4">
115116
<Button color="danger" onClick={handleDiscardAll}>
116117
Discard All
117118
</Button>

packages/bruno-app/src/components/SaveTransientRequest/index.js

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,8 @@ const SaveTransientRequest = ({ item: itemProp, collection: collectionProp, isOp
358358
return null;
359359
}
360360

361+
const showNewFolderFooterButton = !showNewFolderInput && !isSelectingCollection && (filteredFolders.length > 0 && !searchText.trim());
362+
361363
return (
362364
<StyledWrapper>
363365
<Modal
@@ -539,7 +541,7 @@ const SaveTransientRequest = ({ item: itemProp, collection: collectionProp, isOp
539541
size="sm"
540542
onClick={handleCreateNewCollection}
541543
>
542-
Save
544+
Create
543545
</Button>
544546
</div>
545547
</li>
@@ -736,7 +738,20 @@ const SaveTransientRequest = ({ item: itemProp, collection: collectionProp, isOp
736738
</ul>
737739
) : (
738740
<div className="folder-empty-state">
739-
{searchText.trim() ? 'No folders found' : 'No folders available'}
741+
<div className="flex flex-col items-center">
742+
<span>
743+
{searchText.trim() ? 'No folders found' : 'No folders available' }
744+
</span>
745+
<Button
746+
type="button"
747+
color="primary"
748+
variant="ghost"
749+
icon={<IconFolder size={16} strokeWidth={1.5} />}
750+
onClick={handleShowNewFolder}
751+
>
752+
New Folder
753+
</Button>
754+
</div>
740755
</div>
741756
)}
742757
</div>
@@ -747,7 +762,7 @@ const SaveTransientRequest = ({ item: itemProp, collection: collectionProp, isOp
747762

748763
<div className="custom-modal-footer">
749764
<div className="footer-left">
750-
{!showNewFolderInput && !isSelectingCollection && (
765+
{showNewFolderFooterButton && (
751766
<Button
752767
type="button"
753768
color="primary"

packages/bruno-app/src/providers/App/ConfirmAppClose/SaveRequestsModal.js

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { findCollectionByUid, flattenItems, isItemARequest, hasRequestChanges, f
88
import { pluralizeWord } from 'utils/common';
99
import { getInvalidVariableNames } from 'utils/common/variables';
1010
import { completeQuitFlow } from 'providers/ReduxStore/slices/app';
11-
import { saveMultipleRequests, saveMultipleCollections, saveMultipleFolders, saveEnvironment, closeTabs } from 'providers/ReduxStore/slices/collections/actions';
11+
import { saveRequest, saveMultipleRequests, saveMultipleCollections, saveMultipleFolders, saveEnvironment, closeTabs } from 'providers/ReduxStore/slices/collections/actions';
1212
import { saveGlobalEnvironment, clearGlobalEnvironmentDraft } from 'providers/ReduxStore/slices/global-environments';
1313
import { deleteRequestDraft, deleteCollectionDraft, deleteFolderDraft, clearEnvironmentsDraft } from 'providers/ReduxStore/slices/collections';
1414
import { IconAlertTriangle } from '@tabler/icons';
@@ -150,6 +150,8 @@ const SaveRequestsModal = ({ onClose, forceCloseTabs = false, tabUidsToClose = [
150150
const collectionDrafts = allDrafts.filter((d) => d.type === 'collection');
151151
const folderDrafts = allDrafts.filter((d) => d.type === 'folder');
152152
const requestDrafts = allDrafts.filter((d) => isItemARequest(d));
153+
const transientRequestDrafts = requestDrafts.filter((d) => d.isTransient);
154+
const nonTransientRequestDrafts = requestDrafts.filter((d) => !d.isTransient);
153155
const collectionEnvironmentDrafts = allDrafts.filter((d) => d.type === 'collection-environment');
154156
const globalEnvironmentDrafts = allDrafts.filter((d) => d.type === 'global-environment');
155157

@@ -164,8 +166,18 @@ const SaveRequestsModal = ({ onClose, forceCloseTabs = false, tabUidsToClose = [
164166
}
165167

166168
// Save all request drafts
167-
if (requestDrafts.length > 0) {
168-
await dispatch(saveMultipleRequests(requestDrafts));
169+
if (nonTransientRequestDrafts.length > 0) {
170+
await dispatch(saveMultipleRequests(nonTransientRequestDrafts));
171+
}
172+
173+
if (transientRequestDrafts.length > 0) {
174+
await Promise.all(
175+
transientRequestDrafts.map((draft) =>
176+
dispatch(saveRequest(draft.uid, draft.collectionUid, true)).catch(() => null)
177+
)
178+
);
179+
onClose();
180+
return;
169181
}
170182

171183
// Save environment drafts, skipping any with invalid variable names
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { test, expect } from '../../playwright';
2+
import { createCollection, createTransientRequest, fillRequestUrl, closeAllCollections } from '../utils/page';
3+
import { buildCommonLocators } from '../utils/page/locators';
4+
5+
test.describe('Transient Requests - Quit Flow', () => {
6+
test('should open transient save modal when saving during app quit flow', async ({ page, electronApp, createTmpDir }) => {
7+
const locators = buildCommonLocators(page);
8+
const collectionPath = await createTmpDir('transient-quit-flow');
9+
10+
await test.step('Create collection and transient request', async () => {
11+
await createCollection(page, 'transient-quit-flow-test', collectionPath);
12+
await createTransientRequest(page, { requestType: 'HTTP' });
13+
await fillRequestUrl(page, 'http://localhost:8081/ping');
14+
});
15+
16+
await test.step('Trigger app quit flow from main process', async () => {
17+
await electronApp.evaluate(({ BrowserWindow }) => {
18+
for (const win of BrowserWindow.getAllWindows()) {
19+
if (!win.isDestroyed()) {
20+
win.close();
21+
}
22+
}
23+
});
24+
25+
const unsavedChangesModal = page.locator('.bruno-modal-card').filter({ hasText: 'Unsaved changes' });
26+
await expect(unsavedChangesModal).toBeVisible({ timeout: 10000 });
27+
await unsavedChangesModal.getByRole('button', { name: 'Save', exact: true }).click();
28+
});
29+
30+
await test.step('Save transient request using existing Save Request flow', async () => {
31+
const saveTransientModal = page.locator('.bruno-modal-card').filter({ hasText: 'Save Request' });
32+
await expect(saveTransientModal).toBeVisible({ timeout: 10000 });
33+
34+
const requestNameInput = saveTransientModal.locator('#request-name');
35+
await requestNameInput.clear();
36+
await requestNameInput.fill('Saved via quit flow');
37+
38+
await saveTransientModal.getByRole('button', { name: 'Save' }).click();
39+
await expect(page.getByText('Request saved successfully').last()).toBeVisible({ timeout: 10000 });
40+
});
41+
42+
await test.step('Verify app remains open and request is saved', async () => {
43+
await expect(locators.sidebar.collection('transient-quit-flow-test')).toBeVisible();
44+
await locators.sidebar.collection('transient-quit-flow-test').click();
45+
await expect(locators.sidebar.request('Saved via quit flow')).toBeVisible({ timeout: 10000 });
46+
});
47+
48+
await test.step('Cleanup collections', async () => {
49+
await closeAllCollections(page);
50+
});
51+
});
52+
});

0 commit comments

Comments
 (0)