This is an automated email from the ASF dual-hosted git repository.

lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git


The following commit(s) were added to refs/heads/rocketmq-studio by this push:
     new 6817f30f5 fix(metrics): survive blocked browser storage (#5728)
6817f30f5 is described below

commit 6817f30f5c113e56d58b70be1ca10901a2252d01
Author: 0 <[email protected]>
AuthorDate: Mon Oct 19 18:14:10 2026 +0800

    fix(metrics): survive blocked browser storage (#5728)
    
    Verified against `rocketmq-studio` at 86aa49c34 before merging: 缺陷仍在。主干 
`web/src/components/MetricsExplorer.tsx` 恰好 6 处裸 
`localStorage`(`:729`、`:755`、`:901`、`:903`、`:1179`、`:1207`),数目与基线一致,主干这 100 
个提交**没有新增第 7 处**;
    
    merge-tree clean against the current trunk and no outstanding must-fix 
items; merged as-is (squash) per the maintainer review of 2026-10-19.
---
 web/src/components/MetricsExplorer.tsx             | 13 ++++++------
 .../components/__tests__/MetricsExplorer.test.tsx  | 23 ++++++++++++++++++++++
 2 files changed, 30 insertions(+), 6 deletions(-)

diff --git a/web/src/components/MetricsExplorer.tsx 
b/web/src/components/MetricsExplorer.tsx
index a33d2ec86..37001e872 100644
--- a/web/src/components/MetricsExplorer.tsx
+++ b/web/src/components/MetricsExplorer.tsx
@@ -55,6 +55,7 @@ import type { MetricData, MetricMapping, MetricProfile } from 
'../api/metrics';
 import { useLang } from '../i18n/LangContext';
 import { downloadCsv } from '../utils/download';
 import { tableScrollX } from '../utils/table';
+import { readLocalStorage, removeLocalStorage, writeLocalStorage } from 
'../utils/browserStorage';
 import {
   buildMetricCsvFilename,
   buildMetricCsvFromRows,
@@ -726,7 +727,7 @@ const MetricsExplorer = ({ instanceId }: 
MetricsExplorerProps) => {
         // A re-run after an earlier failure must clear the error banner, or 
the recovered
         // panels below would stay hidden behind it until the component is 
remounted.
         setProfileError(false);
-        const storedProfileId = localStorage.getItem(PROFILE_STORAGE_KEY);
+        const storedProfileId = readLocalStorage(PROFILE_STORAGE_KEY);
         const initialProfile =
           nextProfiles.find((profile) => profile.id === storedProfileId) ?? 
nextProfiles[0];
         setProfileId(initialProfile?.id ?? '');
@@ -752,7 +753,7 @@ const MetricsExplorer = ({ instanceId }: 
MetricsExplorerProps) => {
 
   const handleProfileChange = (nextProfileId: string) => {
     const nextProfile = profiles.find((profile) => profile.id === 
nextProfileId);
-    localStorage.setItem(PROFILE_STORAGE_KEY, nextProfileId);
+    writeLocalStorage(PROFILE_STORAGE_KEY, nextProfileId);
     setProfileId(nextProfileId);
     void loadAll(nextProfile, selectedRange);
   };
@@ -898,9 +899,9 @@ const MetricsExplorer = ({ instanceId }: 
MetricsExplorerProps) => {
       rangeIdRef.current = replay.checkpoint.rangeId;
       setCustomPromql(replay.checkpoint.customPromql);
       if (replay.checkpoint.storedProfileId === null) {
-        localStorage.removeItem(PROFILE_STORAGE_KEY);
+        removeLocalStorage(PROFILE_STORAGE_KEY);
       } else {
-        localStorage.setItem(PROFILE_STORAGE_KEY, 
replay.checkpoint.storedProfileId);
+        writeLocalStorage(PROFILE_STORAGE_KEY, 
replay.checkpoint.storedProfileId);
       }
     }
     setPendingDataSource(null);
@@ -1176,7 +1177,7 @@ const MetricsExplorer = ({ instanceId }: 
MetricsExplorerProps) => {
       profileId,
       rangeId,
       customPromql,
-      storedProfileId: localStorage.getItem(PROFILE_STORAGE_KEY),
+      storedProfileId: readLocalStorage(PROFILE_STORAGE_KEY),
     };
     if (entry.profileId === CUSTOM_HISTORY_PROFILE_ID) {
       setRangeId(nextRange.value);
@@ -1204,7 +1205,7 @@ const MetricsExplorer = ({ instanceId }: 
MetricsExplorerProps) => {
 
     setRangeId(nextRange.value);
     setHistoryOpen(false);
-    localStorage.setItem(PROFILE_STORAGE_KEY, nextProfile.id);
+    writeLocalStorage(PROFILE_STORAGE_KEY, nextProfile.id);
     setProfileId(nextProfile.id);
     if (nextDataSource && getDataSourceAuthMode(nextDataSource.auth) !== 
'none') {
       restoreProtectedDataSource(
diff --git a/web/src/components/__tests__/MetricsExplorer.test.tsx 
b/web/src/components/__tests__/MetricsExplorer.test.tsx
index f2fa1c033..a3d2cbcd3 100644
--- a/web/src/components/__tests__/MetricsExplorer.test.tsx
+++ b/web/src/components/__tests__/MetricsExplorer.test.tsx
@@ -284,6 +284,29 @@ describe('MetricsExplorer', () => {
     expect(screen.getByText('42 messages/s')).toBeInTheDocument();
   });
 
+  it('still loads the profiles when storage access is blocked', async () => {
+    // Safari private mode and hardened cookie policies make localStorage 
access throw;
+    // utils/browserStorage exists so every other storage consumer survives 
that. The
+    // explorer must not turn a blocked read into a "profiles failed to load" 
page.
+    const blocked = () => {
+      throw new DOMException('Access is denied for this document', 
'SecurityError');
+    };
+    const getItemSpy = vi.spyOn(Storage.prototype, 
'getItem').mockImplementation(blocked);
+    const setItemSpy = vi.spyOn(Storage.prototype, 
'setItem').mockImplementation(blocked);
+    const removeItemSpy = vi.spyOn(Storage.prototype, 
'removeItem').mockImplementation(blocked);
+
+    renderWithProviders(<MetricsExplorer />);
+
+    expect(await screen.findByText('RocketMQ 5.x Native')).toBeInTheDocument();
+    await waitFor(() =>
+      expect(screen.getByRole('img', { name: 'Message In TPS time series' 
})).toBeInTheDocument(),
+    );
+
+    getItemSpy.mockRestore();
+    setItemSpy.mockRestore();
+    removeItemSpy.mockRestore();
+  });
+
   it('sorts provider samples before drawing and selecting the latest value', 
async () => {
     vi.mocked(queryMetrics).mockResolvedValue({
       ...metricData,

Reply via email to