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 f6decc63c fix(k8s): sort certificate expiry timestamps as UTC (#7319)
f6decc63c is described below
commit f6decc63cbd398301b78449115aa5baafdf657d5
Author: coder999o <[email protected]>
AuthorDate: Mon Oct 26 20:09:23 2026 +0800
fix(k8s): sort certificate expiry timestamps as UTC (#7319)
* fix(k8s): sort certificate expiry timestamps as UTC
---
.../pages/cluster/__tests__/K8sCertsPage.test.tsx | 32 ++++++++++++++++++++++
web/src/pages/cluster/certs.tsx | 4 +--
2 files changed, 34 insertions(+), 2 deletions(-)
diff --git a/web/src/pages/cluster/__tests__/K8sCertsPage.test.tsx
b/web/src/pages/cluster/__tests__/K8sCertsPage.test.tsx
index d94801d47..54954b202 100644
--- a/web/src/pages/cluster/__tests__/K8sCertsPage.test.tsx
+++ b/web/src/pages/cluster/__tests__/K8sCertsPage.test.tsx
@@ -159,6 +159,38 @@ describe('K8sCertsPage', () => {
expect(screen.getAllByText('-').length).toBeGreaterThan(0);
});
+ it.each(['America/New_York', 'Asia/Shanghai', 'UTC'])(
+ 'sortsUtcCertificateExpiriesInBothDirectionsIn%sTest',
+ async (timeZone) => {
+ vi.stubEnv('TZ', timeZone);
+ try {
+ // These are UTC instants, although the wire format has no offset. In
New York,
+ // parsing them as local times normalizes the DST gap's 02:30 to 03:30.
+ vi.mocked(listK8sCerts).mockResolvedValue([
+ { ...certs[0], id: 2, k8sId: 'later-expiry', notAfter:
'2026-03-08T03:00:00' },
+ { ...certs[0], id: 1, k8sId: 'earlier-expiry', notAfter:
'2026-03-08T02:30:00' },
+ ]);
+ const user = userEvent.setup();
+ renderPage();
+ await screen.findByText('earlier-expiry');
+
+ const rowIds = () =>
+ screen
+ .getAllByRole('row')
+ .filter((row) => row.hasAttribute('data-row-key'))
+ .map((row) => row.getAttribute('data-row-key'));
+ const expiryHeader = screen.getByRole('columnheader', { name: /到期时间/
});
+ await user.click(expiryHeader);
+ await waitFor(() => expect(rowIds()).toEqual(['1', '2']));
+
+ await user.click(expiryHeader);
+ await waitFor(() => expect(rowIds()).toEqual(['2', '1']));
+ } finally {
+ vi.unstubAllEnvs();
+ }
+ },
+ );
+
it('explains that certificate records are Studio-local metadata', async ()
=> {
renderPage();
diff --git a/web/src/pages/cluster/certs.tsx b/web/src/pages/cluster/certs.tsx
index fa293a633..d4bc88821 100644
--- a/web/src/pages/cluster/certs.tsx
+++ b/web/src/pages/cluster/certs.tsx
@@ -39,7 +39,7 @@ import InfoBanner from '../../components/InfoBanner';
import type { K8sCertInfo } from '../../api/cluster';
import { listK8sCerts, createK8sCert, deleteK8sCert } from
'../../services/clusterService';
import { describeThrownMessage } from '../../utils/apiError';
-import { formatUtcDateTime } from '../../utils/format';
+import { formatUtcDateTime, parseUtcDateTime } from '../../utils/format';
import { tableScrollX } from '../../utils/table';
import { useLang } from '../../i18n/LangContext';
@@ -211,7 +211,7 @@ const K8sCertsPage = () => {
dataIndex: 'notAfter',
key: 'notAfter',
width: 156,
- sorter: (a, b) => (Date.parse(a.notAfter ?? '') || 0) -
(Date.parse(b.notAfter ?? '') || 0),
+ sorter: (a, b) => (parseUtcDateTime(a.notAfter) || 0) -
(parseUtcDateTime(b.notAfter) || 0),
render: (iso: string | null) => (
<Text type="secondary" style={{ fontSize: 14 }}>
{formatUtcDateTime(iso)}