mirror of
https://github.com/usebruno/bruno.git
synced 2026-07-07 14:08:38 +00:00
feat(snapshot): implement active workspace tab type tracking (#8487)
* feat(snapshot): implement active workspace tab type tracking and restoration * feat(snapshot): introduce workspace tab types and update related logic
This commit is contained in:
@@ -3,7 +3,8 @@ import {
|
||||
serializeTab,
|
||||
serializeActiveTab,
|
||||
getCollectionEnvironmentPath,
|
||||
hydrateSnapshotLookups
|
||||
hydrateSnapshotLookups,
|
||||
WORKSPACE_TAB_TYPES
|
||||
} from 'utils/snapshot';
|
||||
import { normalizePath } from 'utils/common/path';
|
||||
|
||||
@@ -133,6 +134,7 @@ export const serializeSnapshot = async (state, options = {}) => {
|
||||
const existingWorkspace = existingSnapshotLookups.workspacesByPath[normalizePath(workspace.pathname)];
|
||||
|
||||
let lastActiveCollectionPathname = null;
|
||||
let activeWorkspaceTabType = null;
|
||||
|
||||
if (isActiveWorkspace) {
|
||||
const activeTab = tabs.tabs.find((t) => t.uid === tabs.activeTabUid);
|
||||
@@ -144,8 +146,18 @@ export const serializeSnapshot = async (state, options = {}) => {
|
||||
lastActiveCollectionPathname = normalizedPathname && normalizedWorkspacePaths.includes(normalizedPathname)
|
||||
? normalizedPathname
|
||||
: null;
|
||||
|
||||
if (
|
||||
activeTab
|
||||
&& workspace.scratchCollectionUid
|
||||
&& activeTab.collectionUid === workspace.scratchCollectionUid
|
||||
&& WORKSPACE_TAB_TYPES.has(activeTab.type)
|
||||
) {
|
||||
activeWorkspaceTabType = activeTab.type;
|
||||
}
|
||||
} else {
|
||||
lastActiveCollectionPathname = existingWorkspace?.lastActiveCollectionPathname || null;
|
||||
activeWorkspaceTabType = existingWorkspace?.activeWorkspaceTabType || null;
|
||||
}
|
||||
|
||||
const workspaceSorting = isActiveWorkspace
|
||||
@@ -157,6 +169,7 @@ export const serializeSnapshot = async (state, options = {}) => {
|
||||
environment: '',
|
||||
lastActiveCollectionPathname,
|
||||
sorting: workspaceSorting,
|
||||
activeWorkspaceTabType,
|
||||
collections: [...workspaceCollectionPaths]
|
||||
});
|
||||
});
|
||||
|
||||
@@ -125,6 +125,40 @@ describe('shouldPreserveCollectionEnvironmentInSnapshot', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('serializeSnapshot workspace tab restoration', () => {
|
||||
it('records the active workspace tab type when the scratch collection tab is focused', async () => {
|
||||
const scratchCollectionUid = 'scratch-1';
|
||||
const state = makeState();
|
||||
state.workspaces.workspaces[0].scratchCollectionUid = scratchCollectionUid;
|
||||
state.tabs.tabs = [{ uid: `${scratchCollectionUid}-environments`, collectionUid: scratchCollectionUid, type: 'workspaceEnvironments' }];
|
||||
state.tabs.activeTabUid = `${scratchCollectionUid}-environments`;
|
||||
|
||||
const snapshot = await serializeSnapshot(state, {
|
||||
getExistingSnapshot: async () => null
|
||||
});
|
||||
|
||||
expect(snapshot.workspaces[0]).toMatchObject({
|
||||
activeWorkspaceTabType: 'workspaceEnvironments'
|
||||
});
|
||||
});
|
||||
|
||||
it('does not record an active workspace tab type when a non-workspace tab is focused', async () => {
|
||||
const scratchCollectionUid = 'scratch-1';
|
||||
const state = makeState();
|
||||
state.workspaces.workspaces[0].scratchCollectionUid = scratchCollectionUid;
|
||||
state.tabs.tabs = [{ uid: 'req-1', collectionUid: COLLECTION_PATH, type: 'http-request' }];
|
||||
state.tabs.activeTabUid = 'req-1';
|
||||
|
||||
const snapshot = await serializeSnapshot(state, {
|
||||
getExistingSnapshot: async () => null
|
||||
});
|
||||
|
||||
expect(snapshot.workspaces[0]).toMatchObject({
|
||||
activeWorkspaceTabType: null
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('serializeSnapshot collection environment preservation', () => {
|
||||
it('creates a safe first-run snapshot when no existing snapshot is available', async () => {
|
||||
const snapshot = await serializeSnapshot(makeState(), {
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
} from '../app';
|
||||
import { openConsole, closeConsole, setActiveTab as setActiveDevToolsTab, TAB_IDENFIERS as DEVTOOL_TABS } from '../logs';
|
||||
import { normalizePath } from 'utils/common/path';
|
||||
import { hydrateTabs, getActiveTabFromSnapshot, hydrateSnapshotLookups, getCollectionSnapshotFromLookups } from 'utils/snapshot';
|
||||
import { hydrateTabs, getActiveTabFromSnapshot, hydrateSnapshotLookups, getCollectionSnapshotFromLookups, WORKSPACE_TAB_UID_SUFFIX_BY_TYPE } from 'utils/snapshot';
|
||||
import toast from 'react-hot-toast';
|
||||
import { closeAiSidebar } from '../chat';
|
||||
|
||||
@@ -640,10 +640,23 @@ export const switchWorkspace = (workspaceUid) => {
|
||||
));
|
||||
}));
|
||||
|
||||
let requestedWorkspaceTabType = null;
|
||||
|
||||
// Add workspace tabs
|
||||
if (scratchCollection?.uid) {
|
||||
dispatch(addTab({ uid: `${scratchCollection.uid}-overview`, collectionUid: scratchCollection.uid, type: 'workspaceOverview' }));
|
||||
dispatch(addTab({ uid: `${scratchCollection.uid}-environments`, collectionUid: scratchCollection.uid, type: 'workspaceEnvironments' }));
|
||||
|
||||
requestedWorkspaceTabType = workspaceSnapshot?.activeWorkspaceTabType;
|
||||
const requestedWorkspaceTabSuffix = WORKSPACE_TAB_UID_SUFFIX_BY_TYPE[requestedWorkspaceTabType];
|
||||
if (requestedWorkspaceTabSuffix) {
|
||||
const requestedWorkspaceTabUid = `${scratchCollection.uid}-${requestedWorkspaceTabSuffix}`;
|
||||
dispatch(addTab({
|
||||
uid: requestedWorkspaceTabUid,
|
||||
collectionUid: scratchCollection.uid,
|
||||
type: requestedWorkspaceTabType
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
// Restore active collection from snapshot using lastActiveCollectionPathname
|
||||
@@ -676,10 +689,10 @@ export const switchWorkspace = (workspaceUid) => {
|
||||
|
||||
if (activeTab) {
|
||||
dispatch(addTab(activeTab));
|
||||
} else if (scratchCollection?.uid) {
|
||||
} else if (scratchCollection?.uid && !requestedWorkspaceTabType) {
|
||||
dispatch(addTab({ uid: `${scratchCollection.uid}-overview`, collectionUid: scratchCollection.uid, type: 'workspaceOverview' }));
|
||||
}
|
||||
} else if (scratchCollection?.uid) {
|
||||
} else if (scratchCollection?.uid && !requestedWorkspaceTabType) {
|
||||
// No active collection, focus the workspace overview tab
|
||||
dispatch(addTab({ uid: `${scratchCollection.uid}-overview`, collectionUid: scratchCollection.uid, type: 'workspaceOverview' }));
|
||||
}
|
||||
|
||||
@@ -30,6 +30,13 @@ const IGNORED_TAB_TYPES = new Set([
|
||||
'v4-migration'
|
||||
]);
|
||||
|
||||
export const WORKSPACE_TAB_UID_SUFFIX_BY_TYPE = {
|
||||
workspaceOverview: 'overview',
|
||||
workspaceEnvironments: 'environments'
|
||||
};
|
||||
|
||||
export const WORKSPACE_TAB_TYPES = new Set(Object.keys(WORKSPACE_TAB_UID_SUFFIX_BY_TYPE));
|
||||
|
||||
export const SAVE_TRIGGERS = new Map([
|
||||
['app/setSnapshotReady', null],
|
||||
['tabs/addTab', null],
|
||||
@@ -143,6 +150,9 @@ const normalizeWorkspaceSnapshotEntry = (pathname, entry = {}) => {
|
||||
? entry.lastActiveCollectionPathname
|
||||
: null,
|
||||
sorting: typeof entry.sorting === 'string' ? entry.sorting : 'default',
|
||||
activeWorkspaceTabType: WORKSPACE_TAB_TYPES.has(entry.activeWorkspaceTabType)
|
||||
? entry.activeWorkspaceTabType
|
||||
: null,
|
||||
collections
|
||||
};
|
||||
};
|
||||
@@ -226,6 +236,7 @@ export const hydrateSnapshotLookups = (snapshot = {}) => {
|
||||
pathname: workspace.pathname,
|
||||
lastActiveCollectionPathname: workspace.lastActiveCollectionPathname,
|
||||
sorting: workspace.sorting,
|
||||
activeWorkspaceTabType: workspace.activeWorkspaceTabType,
|
||||
collections: workspace.collections
|
||||
};
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ const yup = require('yup');
|
||||
|
||||
const SNAPSHOT_VERSION = '0.0.1';
|
||||
const ENV_FILE_EXTENSIONS = ['bru', 'yml', 'yaml'];
|
||||
const WORKSPACE_TAB_TYPES = ['workspaceOverview', 'workspaceEnvironments'];
|
||||
|
||||
const isObject = (value) => value && typeof value === 'object' && !Array.isArray(value);
|
||||
|
||||
@@ -71,6 +72,7 @@ const workspaceSchema = yup.object({
|
||||
pathname: yup.string().required(),
|
||||
environment: yup.string().defined(),
|
||||
lastActiveCollectionPathname: yup.string().nullable(),
|
||||
activeWorkspaceTabType: yup.string().oneOf([...WORKSPACE_TAB_TYPES, null]).nullable(),
|
||||
sorting: yup.mixed().oneOf(['alphabetical', 'reverseAlphabetical', 'default']),
|
||||
collections: yup.array().of(yup.string()).optional()
|
||||
});
|
||||
@@ -193,6 +195,7 @@ class SnapshotManager {
|
||||
this.store.delete('activeWorkspacePath');
|
||||
this.store.set('workspaces', (this.store.store?.workspaces ?? []).map((d) => {
|
||||
d.lastActiveCollectionPathname = undefined;
|
||||
d.activeWorkspaceTabType = undefined;
|
||||
return d;
|
||||
}));
|
||||
this.store.set('collections', (this.store.store?.collections ?? []).map((d) => {
|
||||
@@ -461,6 +464,9 @@ class SnapshotManager {
|
||||
lastActiveCollectionPathname: typeof workspace.lastActiveCollectionPathname === 'string'
|
||||
? workspace.lastActiveCollectionPathname
|
||||
: null,
|
||||
activeWorkspaceTabType: WORKSPACE_TAB_TYPES.includes(workspace.activeWorkspaceTabType)
|
||||
? workspace.activeWorkspaceTabType
|
||||
: null,
|
||||
sorting: typeof workspace.sorting === 'string' ? workspace.sorting : 'default',
|
||||
collections
|
||||
};
|
||||
|
||||
@@ -55,4 +55,32 @@ test.describe('Snapshot: Global Tab Restoration', () => {
|
||||
await closeElectronApp(app2);
|
||||
});
|
||||
});
|
||||
|
||||
test('workspace-level active tab is restored after restart', async ({ launchElectronApp, createTmpDir }) => {
|
||||
const userDataPath = await createTmpDir('snap-workspace-active-tab');
|
||||
|
||||
const app = await launchElectronApp({ userDataPath });
|
||||
const page = await waitForReadyPage(app);
|
||||
|
||||
await test.step('Switch to workspace environments tab', async () => {
|
||||
const locators = buildCommonLocators(page);
|
||||
await locators.tabs.requestTab('Environments').click();
|
||||
await expect(locators.tabs.activeRequestTab()).toContainText('Environments');
|
||||
});
|
||||
|
||||
await test.step('Close and restart app', async () => {
|
||||
await page.waitForTimeout(2000);
|
||||
await closeElectronApp(app);
|
||||
});
|
||||
|
||||
await test.step('Verify workspace environments is restored as active tab', async () => {
|
||||
const app2 = await launchElectronApp({ userDataPath });
|
||||
const page2 = await waitForReadyPage(app2);
|
||||
|
||||
const locators2 = buildCommonLocators(page2);
|
||||
await expect(locators2.tabs.activeRequestTab()).toContainText('Environments', { timeout: 15000 });
|
||||
|
||||
await closeElectronApp(app2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user