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)