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 e7bacd22e fix(studio): render session timestamps in the viewer 
timezone (#4576)
e7bacd22e is described below

commit e7bacd22e637db1a440ff1131d34d8dfa313588c
Author: Zhao Jianing <[email protected]>
AuthorDate: Mon Sep 21 21:02:41 2026 +0800

    fix(studio): render session timestamps in the viewer timezone (#4576)
    
    `UserManagement.tsx` formatted every timestamp through a local `dateTime` 
helper built on `new Date(value).toLocaleString()`. `StudioUserVO` and 
`StudioUserSessionDetailVO` carry `LocalDateTime` fields produced against a UTC 
clock and serialized without `WRITE_DATES_AS_TIMESTAMPS`, so the wire value is 
an ISO string with no offset — which ECMAScript parses in the viewer's local 
zone. Last-seen, expiry and password-change times were therefore shifted by the 
viewer's offset from UTC in  [...]
    
    `dateTime` now delegates to the existing `formatUtcDateTime` helper that 
ops/alerts, notification deliveries, system alerts and the message history 
drawer already use for this payload shape, which covers all eight call sites 
and the six table columns rendered through it in one place. The test that 
asserted `new Date(...).toLocaleString()` — the same expression as the 
implementation, so green in every timezone — now asserts the helper's output, 
plus a case pinned with `vi.stubEnv('TZ', [...]
---
 web/src/pages/studio/UserManagement.tsx            |  6 ++++--
 .../pages/studio/__tests__/UserManagement.test.tsx | 25 +++++++++++++++++++++-
 2 files changed, 28 insertions(+), 3 deletions(-)

diff --git a/web/src/pages/studio/UserManagement.tsx 
b/web/src/pages/studio/UserManagement.tsx
index bec63c911..e5bf179ff 100644
--- a/web/src/pages/studio/UserManagement.tsx
+++ b/web/src/pages/studio/UserManagement.tsx
@@ -61,7 +61,7 @@ import {
 } from '../../api/studioUsers';
 import useAuthStore from '../../stores/authStore';
 import { buildCsv, downloadCsv, type CsvColumn } from '../../utils/download';
-import { formatDelay } from '../../utils/format';
+import { formatDelay, formatUtcDateTime } from '../../utils/format';
 import { tableScrollX } from '../../utils/table';
 
 interface CreateFormValues {
@@ -75,7 +75,9 @@ interface PasswordFormValues {
   newPassword: string;
 }
 
-const dateTime = (value?: string | null) => (value ? new 
Date(value).toLocaleString() : '-');
+// Studio user and session APIs serialize UTC LocalDateTime values without an 
offset,
+// so the timestamps have to be parsed as UTC before rendering in the viewer's 
zone.
+const dateTime = (value?: string | null) => formatUtcDateTime(value);
 const durationText = (value?: number | null) => (value == null ? '-' : 
formatDelay(value));
 const PAGE_SIZE_OPTIONS = [20, 50, 100];
 
diff --git a/web/src/pages/studio/__tests__/UserManagement.test.tsx 
b/web/src/pages/studio/__tests__/UserManagement.test.tsx
index 22a74a9d6..91643c1aa 100644
--- a/web/src/pages/studio/__tests__/UserManagement.test.tsx
+++ b/web/src/pages/studio/__tests__/UserManagement.test.tsx
@@ -31,6 +31,7 @@ import {
   type StudioUserSessionDetail,
 } from '../../../api/studioUsers';
 import { downloadCsv } from '../../../utils/download';
+import { formatUtcDateTime } from '../../../utils/format';
 import UserManagementPage from '../UserManagement';
 
 type MockAuthState = { admin: boolean; userId: number; logout: () => void };
@@ -279,7 +280,7 @@ describe('UserManagementPage', () => {
 
     await screen.findByText('operator');
     expect(screen.getAllByText('2').length).toBeGreaterThan(0);
-    expect(screen.getByText(new 
Date('2026-08-22T09:30:00').toLocaleString())).toBeInTheDocument();
+    
expect(screen.getByText(formatUtcDateTime('2026-08-22T09:30:00'))).toBeInTheDocument();
 
     await user.click(screen.getByRole('button', { name: '注销' }));
     await screen.findByText('注销 operator 的活跃会话?');
@@ -289,6 +290,28 @@ describe('UserManagementPage', () => {
     expect(listStudioUsers).toHaveBeenCalledTimes(2);
   });
 
+  it('renders session timestamps as UTC values in the viewer timezone', async 
() => {
+    // The user/session APIs serialize UTC LocalDateTime values without an 
offset, so
+    // the table and drawer must not parse them as browser-local wall times. 
Pin the
+    // viewer zone so the expectation discriminates regardless of the runner's 
zone.
+    vi.stubEnv('TZ', 'Asia/Shanghai');
+    try {
+      const user = userEvent.setup({ pointerEventsCheck: 0 });
+      renderPage();
+
+      await screen.findByText('operator');
+      // 09:30 UTC is 17:30 in Asia/Shanghai; a browser-local misparse shows 
09:30.
+      expect(screen.getByText('2026-08-22 17:30:00 
GMT+8')).toBeInTheDocument();
+
+      await user.click(screen.getByRole('button', { name: '会话' }));
+      const drawer = await screen.findByRole('dialog', { name: 'operator 的会话' 
});
+      expect(within(drawer).getByText('2026-08-22 17:45:00 
GMT+8')).toBeInTheDocument();
+      expect(within(drawer).getByText('2026-08-22 18:00:00 
GMT+8')).toBeInTheDocument();
+    } finally {
+      vi.unstubAllEnvs();
+    }
+  });
+
   it('revokes sessions from the detail drawer and refreshes the detail list', 
async () => {
     vi.mocked(listStudioUserSessions)
       .mockResolvedValueOnce(sessionDetails)

Reply via email to