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 bd7718137 fix(metrics): re-run the custom query when the range changes 
(#4901)
bd7718137 is described below

commit bd77181379aee9348f46518d86017070f3a639da
Author: 烤化の初雪 <[email protected]>
AuthorDate: Thu Sep 24 18:21:11 2026 +0800

    fix(metrics): re-run the custom query when the range changes (#4901)
    
    test(metrics): pin that the custom re-run leaves the panel flow alone
    
    The fix re-runs the committed custom query on a range change, which now sits
    beside the profile panels' own loadAll — the cross-flow interaction tracked 
by
    issue #3304 and fixed by #3299. The regression test also asserts that both
    flows publish their result after the range change, with the two payloads
    distinguished by their values so a stranded panel fails the assertion.
    
    Checked that the assertion has teeth: making runCustomQuery bump
    panelRequestIdRef as well (the shared-generation freeze of #3304) makes it 
fail
    with "Unable to find an element with the text: 77 messages/s", while the 
fix as
    written keeps the two request generations separate.
    
    fix(metrics): re-run the custom query when the range changes
    
    The range control scopes the whole explorer, and every other path that 
changes
    the window re-runs the committed custom query: the refresh button does it
    (MetricsExplorer.tsx:1354-1357) and an instance switch does it (:878-880).
    handleRangeChange only called loadAll, so after committing a custom 
expression
    and picking another range the profile panels moved to the new window while 
the
    custom panel kept the samples, the query-window popover and the CSV 
metadata of
    the previous one — the visible range control disagreed with the panel under 
it.
    
    Re-run the committed custom query with the next range, mirroring the refresh
    handler. handleRangeChange moves below runCustomQuery so it can call it.
    
    Regression test: a committed custom query is re-issued with the 6h window
    (start 1799978400, step 2m) when the range changes, failing before the 
change
    with "expected [...] to have a length of 2 but got 1".
---
 web/src/components/MetricsExplorer.tsx             | 19 ++++---
 .../components/__tests__/MetricsExplorer.test.tsx  | 66 ++++++++++++++++++++++
 2 files changed, 78 insertions(+), 7 deletions(-)

diff --git a/web/src/components/MetricsExplorer.tsx 
b/web/src/components/MetricsExplorer.tsx
index cb1c0fa50..b4c37865a 100644
--- a/web/src/components/MetricsExplorer.tsx
+++ b/web/src/components/MetricsExplorer.tsx
@@ -723,13 +723,6 @@ const MetricsExplorer = ({ instanceId }: 
MetricsExplorerProps) => {
     void loadAll(nextProfile, selectedRange);
   };
 
-  const handleRangeChange = (nextRangeId: RangeOption['value']) => {
-    const nextRange =
-      RANGE_OPTIONS.find((range) => range.value === nextRangeId) ?? 
RANGE_OPTIONS[0];
-    setRangeId(nextRangeId);
-    void loadAll(selectedProfile, nextRange);
-  };
-
   const runCustomQuery = useCallback(
     async (promql: string, range: RangeOption) => {
       const trimmed = promql.trim();
@@ -795,6 +788,18 @@ const MetricsExplorer = ({ instanceId }: 
MetricsExplorerProps) => {
     [copy.customTitle, instanceId, queryErrorFallback, runQuery],
   );
 
+  const handleRangeChange = (nextRangeId: RangeOption['value']) => {
+    const nextRange =
+      RANGE_OPTIONS.find((range) => range.value === nextRangeId) ?? 
RANGE_OPTIONS[0];
+    setRangeId(nextRangeId);
+    void loadAll(selectedProfile, nextRange);
+    // The range control scopes the whole explorer, so a committed custom 
query follows the new
+    // window the same way the refresh button and an instance switch already 
make it follow.
+    if (appliedCustomPromql) {
+      void runCustomQuery(appliedCustomPromql, nextRange);
+    }
+  };
+
   const activateDataSource = (
     nextKey: string,
     credentials?: AuthFormValues,
diff --git a/web/src/components/__tests__/MetricsExplorer.test.tsx 
b/web/src/components/__tests__/MetricsExplorer.test.tsx
index bf63839d7..c353f9c57 100644
--- a/web/src/components/__tests__/MetricsExplorer.test.tsx
+++ b/web/src/components/__tests__/MetricsExplorer.test.tsx
@@ -504,6 +504,72 @@ describe('MetricsExplorer', () => {
     expect(screen.getByText('cluster=prod / 
query=custom')).toBeInTheDocument();
   });
 
+  it('re-runs the committed custom query when the range changes', async () => {
+    const user = userEvent.setup();
+    renderWithProviders(<MetricsExplorer />);
+    await screen.findByRole('img', { name: 'Message In TPS time series' });
+
+    await user.type(screen.getByLabelText('自定义查询'), 
'sum(rocketmq_topic_number)');
+    await user.click(screen.getByRole('button', { name: '查询' }));
+    await waitFor(() =>
+      expect(queryMetrics).toHaveBeenCalledWith({
+        metric: 'sum(rocketmq_topic_number)',
+        start: 1_799_996_400,
+        end: 1_800_000_000,
+        step: '30s',
+      }),
+    );
+
+    // The range control governs the whole explorer: the profile panels 
re-query through
+    // loadAll, so the custom panel has to follow the new window as well 
instead of
+    // keeping the samples its previous window produced.
+    const refreshedProfileData = {
+      ...metricData,
+      series: [
+        {
+          ...metricData.series[0],
+          values: [{ timestamp: 1_800_000_000, value: '77' }],
+        },
+      ],
+    };
+    const refreshedCustomData = {
+      ...metricData,
+      series: [
+        {
+          ...metricData.series[0],
+          labels: { cluster: 'prod', query: 'custom' },
+          values: [{ timestamp: 1_800_000_000, value: '9' }],
+        },
+      ],
+    };
+    vi.mocked(queryMetrics).mockImplementation((query) =>
+      Promise.resolve(
+        query.metric === 'sum(rocketmq_topic_number)' ? refreshedCustomData : 
refreshedProfileData,
+      ),
+    );
+
+    await user.click(screen.getByText('6h'));
+
+    const customCalls = () =>
+      vi
+        .mocked(queryMetrics)
+        .mock.calls.filter((call) => call[0].metric === 
'sum(rocketmq_topic_number)');
+    await waitFor(() => expect(customCalls()).toHaveLength(2));
+    const rerun = customCalls()[customCalls().length - 1];
+    expect(rerun[0]).toEqual({
+      metric: 'sum(rocketmq_topic_number)',
+      start: 1_799_978_400,
+      end: 1_800_000_000,
+      step: '2m',
+    });
+
+    // The custom panel re-runs beside the profile panels, so the added call 
must leave the
+    // other flow's request generation alone: both still publish their own 
result. (The
+    // cross-flow freeze of issue #3304, fixed by #3299, was the opposite 
behaviour.)
+    expect(await screen.findByText('77 messages/s')).toBeInTheDocument();
+    expect(screen.getByText('cluster=prod / 
query=custom')).toBeInTheDocument();
+  });
+
   it('queries the first metric when the version profile changes', async () => {
     const user = userEvent.setup();
     renderWithProviders(<MetricsExplorer />);

Reply via email to