From a2b5fda66103ec277a251ba6002b9c3b02dbbccb Mon Sep 17 00:00:00 2001 From: Mallikarjuna Reddy Nimmakayala Date: Tue, 30 Jun 2026 22:51:31 +0530 Subject: [PATCH] fix(Table Chart): Show correct cache time moment for Query2 (#37482) Co-authored-by: Evan Rusackas Co-authored-by: Enzo Martellucci <52219496+EnxDev@users.noreply.github.com> Co-authored-by: Amin Ghadersohi Co-authored-by: Claude Opus 4.8 --- .../SliceHeaderControls.test.tsx | 112 ++++++++++++++++++ .../components/SliceHeaderControls/index.tsx | 15 ++- 2 files changed, 123 insertions(+), 4 deletions(-) diff --git a/superset-frontend/src/dashboard/components/SliceHeaderControls/SliceHeaderControls.test.tsx b/superset-frontend/src/dashboard/components/SliceHeaderControls/SliceHeaderControls.test.tsx index bc3adeb1a33..571f0f695e1 100644 --- a/superset-frontend/src/dashboard/components/SliceHeaderControls/SliceHeaderControls.test.tsx +++ b/superset-frontend/src/dashboard/components/SliceHeaderControls/SliceHeaderControls.test.tsx @@ -674,3 +674,115 @@ test('Should pass formData to Share menu for embed code feature', () => { openMenu(); expect(screen.getByText('Share')).toBeInTheDocument(); }); + +test('Should show single fetched query tooltip with timestamp', async () => { + const updatedDttm = Date.parse('2024-01-28T10:00:00.000Z'); + const props = createProps(); + props.isCached = [false]; + props.cachedDttm = ['']; + props.updatedDttm = updatedDttm; + + renderWrapper(props); + openMenu(); + + const refreshButton = screen.getByText('Force refresh'); + expect(refreshButton).toBeInTheDocument(); + + userEvent.hover(refreshButton); + expect(await screen.findByText(/Fetched/)).toBeInTheDocument(); +}); + +test('Should show single cached query tooltip with timestamp', async () => { + const cachedDttm = '2024-01-28T10:00:00.000Z'; + const props = createProps(); + props.isCached = [true]; + props.cachedDttm = [cachedDttm]; + props.updatedDttm = null; + + renderWrapper(props); + openMenu(); + + const refreshButton = screen.getByText('Force refresh'); + expect(refreshButton).toBeInTheDocument(); + + userEvent.hover(refreshButton); + expect(await screen.findByText(/Cached/)).toBeInTheDocument(); +}); + +test('Should show multiple per-query tooltips when all queries are fetched', async () => { + const cachedDttm1 = ''; + const cachedDttm2 = ''; + const updatedDttm = Date.parse('2024-01-28T10:10:00.000Z'); + const props = createProps(VizType.Table); + props.isCached = [false, false]; + props.cachedDttm = [cachedDttm1, cachedDttm2]; + props.updatedDttm = updatedDttm; + + renderWrapper(props); + openMenu(); + + const refreshButton = screen.getByText('Force refresh'); + expect(refreshButton).toBeInTheDocument(); + + userEvent.hover(refreshButton); + expect(await screen.findByText(/Fetched/)).toBeInTheDocument(); +}); + +test('Should show multiple per-query tooltips when all queries are cached', async () => { + const cachedDttm1 = '2025-01-28T10:00:00.000Z'; + const cachedDttm2 = '2024-01-28T10:05:00.000Z'; + const props = createProps(VizType.Table); + props.isCached = [true, true]; + props.cachedDttm = [cachedDttm1, cachedDttm2]; + props.updatedDttm = null; + + renderWrapper(props); + openMenu(); + + const refreshButton = screen.getByText('Force refresh'); + expect(refreshButton).toBeInTheDocument(); + + userEvent.hover(refreshButton); + expect(await screen.findByText(/Query 1: Cached/)).toBeInTheDocument(); + expect(await screen.findByText(/Query 2: Cached/)).toBeInTheDocument(); +}); + +test('Should deduplicate identical cache times in tooltip', async () => { + const sameCachedDttm = '2024-01-28T10:00:00.000Z'; + const props = createProps(VizType.Table); + props.isCached = [true, true]; + props.cachedDttm = [sameCachedDttm, sameCachedDttm]; + props.updatedDttm = null; + + renderWrapper(props); + openMenu(); + + const refreshButton = screen.getByText('Force refresh'); + expect(refreshButton).toBeInTheDocument(); + + userEvent.hover(refreshButton); + expect(await screen.findByText(/Cached/)).toBeInTheDocument(); +}); + +test('Should handle three or more queries with different cache states', async () => { + const cachedDttm1 = '2024-01-28T10:00:00.000Z'; + const cachedDttm2 = '2024-01-28T10:05:00.000Z'; + const cachedDttm3 = ''; + const updatedDttm = Date.parse('2024-01-28T10:15:00.000Z'); + const props = createProps(VizType.Table); + props.isCached = [true, false, true]; + props.cachedDttm = [cachedDttm1, cachedDttm2, cachedDttm3]; + props.updatedDttm = updatedDttm; + + renderWrapper(props); + openMenu(); + + const refreshButton = screen.getByText('Force refresh'); + expect(refreshButton).toBeInTheDocument(); + + userEvent.hover(refreshButton); + + expect(await screen.findByText(/Query 1:/)).toBeInTheDocument(); + expect(await screen.findByText(/Query 2:/)).toBeInTheDocument(); + expect(await screen.findByText(/Query 3:/)).toBeInTheDocument(); +}); diff --git a/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx b/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx index 508b83c0bc1..a903787e215 100644 --- a/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx +++ b/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx @@ -380,17 +380,24 @@ const SliceHeaderControls = ( const updatedWhen = updatedDttm ? (extendedDayjs.utc(updatedDttm) as any).fromNow() : ''; - const getCachedTitle = (itemCached: boolean) => { + const getCachedTitle = (itemCached: boolean, index: number) => { if (itemCached) { - return t('Cached %s', cachedWhen); + return t('Cached %s', cachedWhen[index]); } if (updatedWhen) { return t('Fetched %s', updatedWhen); } return ''; }; - const refreshTooltipData = [...new Set(isCached.map(getCachedTitle) || '')]; - // If all queries have same cache time we can unit them to one + const refreshTooltipData = (() => { + const titles = isCached.map((itemCached, index) => + getCachedTitle(itemCached, index), + ); + // Collapse to a single entry only when every query shares the same + // cache/fetch time; otherwise keep the per-query list so the "Query N" + // numbering stays aligned with the original query order. + return new Set(titles).size === 1 ? [titles[0]] : titles; + })(); const refreshTooltip = refreshTooltipData.map((item, index) => (
{refreshTooltipData.length > 1