From 02235cdd9de2d897e68177c29ae82591982b2e6f Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Wed, 21 May 2025 17:12:37 +0530 Subject: [PATCH] chore(ui): cache version response for any hour to avoid frequent calls (#21323) * chore(ui): cache version response for any hour to avoid frequent calls * fix tests --- .../ui/src/components/NavBar/NavBar.test.tsx | 102 +++++++++++++----- .../ui/src/components/NavBar/NavBar.tsx | 15 +++ .../common/CmdKIcon/CmdKIcon.component.tsx | 49 --------- .../common/CmdKIcon/CmdKIcon.test.tsx | 48 --------- .../resources/ui/src/constants/constants.ts | 3 + 5 files changed, 95 insertions(+), 122 deletions(-) delete mode 100644 openmetadata-ui/src/main/resources/ui/src/components/common/CmdKIcon/CmdKIcon.component.tsx delete mode 100644 openmetadata-ui/src/main/resources/ui/src/components/common/CmdKIcon/CmdKIcon.test.tsx diff --git a/openmetadata-ui/src/main/resources/ui/src/components/NavBar/NavBar.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/NavBar/NavBar.test.tsx index e67df02b71b..b0ee2238085 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/NavBar/NavBar.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/NavBar/NavBar.test.tsx @@ -10,13 +10,31 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { render, screen } from '@testing-library/react'; +import { act, render, screen } from '@testing-library/react'; import React from 'react'; +import { ONE_HOUR_MS } from '../../constants/constants'; import { HELP_ITEMS_ENUM } from '../../constants/Navbar.constants'; - import { getVersion } from '../../rest/miscAPI'; import { getHelpDropdownItems } from '../../utils/NavbarUtils'; -import NavBar from './NavBar'; +import NavBarComponent from './NavBar'; + +// Place these at the very top of your test file, before any imports! +const mockGetItem = jest.fn(); +const mockSetItem = jest.fn(); + +jest.mock('cookie-storage', () => ({ + CookieStorage: class { + getItem(...args: any[]) { + return mockGetItem(...args); + } + setItem(...args: any[]) { + return mockSetItem(...args); + } + constructor() { + // Do nothing + } + }, +})); jest.mock('../../hooks/useApplicationStore', () => ({ useApplicationStore: jest.fn().mockImplementation(() => ({ @@ -99,15 +117,6 @@ jest.mock('react-router-dom', () => ({ useHistory: jest.fn(), })); -jest.mock('../common/CmdKIcon/CmdKIcon.component', () => { - return jest.fn().mockReturnValue(
CmdKIcon
); -}); -jest.mock('../AppBar/SearchOptions', () => { - return jest.fn().mockReturnValue(
SearchOptions
); -}); -jest.mock('../AppBar/Suggestions', () => { - return jest.fn().mockReturnValue(
Suggestions
); -}); jest.mock('antd', () => ({ ...jest.requireActual('antd'), @@ -120,16 +129,6 @@ jest.mock('antd', () => ({ }), })); -jest.mock('../../rest/miscAPI', () => ({ - getVersion: jest.fn().mockImplementation(() => - Promise.resolve({ - data: { - version: '0.5.0-SNAPSHOT', - }, - }) - ), -})); - jest.mock('../../utils/NavbarUtils', () => ({ getHelpDropdownItems: jest.fn().mockReturnValue([ { @@ -139,9 +138,15 @@ jest.mock('../../utils/NavbarUtils', () => ({ ]), })); +jest.mock('../../rest/miscAPI', () => ({ + getVersion: jest.fn().mockResolvedValue({ + version: '0.5.0-SNAPSHOT', + }), +})); + describe('Test NavBar Component', () => { it('Should render NavBar component', async () => { - render(); + render(); expect(await screen.findByTestId('global-search-bar')).toBeInTheDocument(); expect(await screen.findByTestId('user-profile-icon')).toBeInTheDocument(); @@ -160,14 +165,61 @@ describe('Test NavBar Component', () => { }); it('should call getVersion onMount', () => { - render(); + render(); expect(getVersion).toHaveBeenCalled(); }); it('should call getHelpDropdownItems function', async () => { - render(); + render(); expect(getHelpDropdownItems).toHaveBeenCalled(); }); }); + +// --- Additional tests for fetchOMVersion one hour threshold --- +describe('fetchOMVersion one hour threshold', () => { + const OLD_DATE_NOW = Date.now; + + beforeEach(() => { + jest.resetModules(); + + global.Date.now = jest.fn(); + }); + + afterEach(() => { + global.Date.now = OLD_DATE_NOW; + }); + + it('should NOT call getVersion if less than one hour since last fetch', async () => { + const now = 2000000; + const lastFetch = now - (ONE_HOUR_MS - 1000); // less than 1 hour ago + mockGetItem.mockReturnValue(String(lastFetch)); + jest.spyOn(global.Date, 'now').mockReturnValue(now); + + render(); + await screen.findByTestId('global-search-bar'); + + expect(getVersion).not.toHaveBeenCalled(); + }); + + it('should call getVersion and setItem if more than one hour since last fetch', async () => { + const now = 3000000; + const lastFetch = now - (ONE_HOUR_MS + 1000); // more than 1 hour ago + mockGetItem.mockReturnValue(String(lastFetch)); + (global.Date.now as jest.Mock).mockReturnValue(now); + + render(); + await Promise.resolve(); + + await act(async () => { + expect(getVersion).toHaveBeenCalled(); + }); + + expect(mockSetItem).toHaveBeenCalledWith( + 'versionFetchTime', + '3000000', + expect.objectContaining({ expires: expect.any(Date) }) + ); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/NavBar/NavBar.tsx b/openmetadata-ui/src/main/resources/ui/src/components/NavBar/NavBar.tsx index 40abdaeb4d3..3f1d678d5da 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/NavBar/NavBar.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/NavBar/NavBar.tsx @@ -46,7 +46,9 @@ import { ReactComponent as SidebarExpandedIcon } from '../../assets/svg/ic-sideb import { DEFAULT_DOMAIN_VALUE, NOTIFICATION_READ_TIMER, + ONE_HOUR_MS, SOCKET_EVENTS, + VERSION_FETCH_TIME_KEY, } from '../../constants/constants'; import { GlobalSettingsMenuCategory } from '../../constants/GlobalSettings.constants'; import { HELP_ITEMS_ENUM } from '../../constants/Navbar.constants'; @@ -124,9 +126,22 @@ const NavBar = () => { } = useCurrentUserPreferences(); const fetchOMVersion = async () => { + // If version fetch happens within an hour, skip fetching + const lastFetchTime = cookieStorage.getItem(VERSION_FETCH_TIME_KEY); + const now = Date.now(); + + if (lastFetchTime && now - Number(lastFetchTime) < ONE_HOUR_MS) { + // Less than an hour since last fetch, skip fetching + return; + } + try { const res = await getVersion(); setVersion(res.version); + // Set/update the cookie with current time, expires in 1 hour + cookieStorage.setItem(VERSION_FETCH_TIME_KEY, String(now), { + expires: new Date(now + ONE_HOUR_MS), + }); } catch (err) { showErrorToast( err as AxiosError, diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/CmdKIcon/CmdKIcon.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/CmdKIcon/CmdKIcon.component.tsx deleted file mode 100644 index 41304336042..00000000000 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/CmdKIcon/CmdKIcon.component.tsx +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2022 Collate. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React from 'react'; -import { ReactComponent as CmdButton } from '../../../assets/svg/command-button.svg'; -import { ReactComponent as CtrlButton } from '../../../assets/svg/control-button.svg'; -import { ReactComponent as KButton } from '../../../assets/svg/k-button.svg'; -import { NavigatorHelper } from '../../../utils/NavigatorUtils'; - -const CmdKIcon = () => { - return ( -
- {NavigatorHelper.isMacOs() ? ( - - ) : ( - - )} - -
- ); -}; - -export default CmdKIcon; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/CmdKIcon/CmdKIcon.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/CmdKIcon/CmdKIcon.test.tsx deleted file mode 100644 index 1758096d98c..00000000000 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/CmdKIcon/CmdKIcon.test.tsx +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2024 Collate. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { act, render, screen } from '@testing-library/react'; -import React from 'react'; -import { NavigatorHelper } from '../../../utils/NavigatorUtils'; -import CmdKIcon from './CmdKIcon.component'; - -jest.mock('../../../utils/NavigatorUtils', () => ({ - NavigatorHelper: { - isMacOs: jest.fn(), - }, -})); - -describe('CmdKIcon', () => { - it('should render CmdKIcon', async () => { - await act(async () => { - render(); - }); - - expect(screen.getByTestId('cmdicon-container')).toBeInTheDocument(); - }); - - it('should render CmdButton when isMacOs is true', () => { - (NavigatorHelper.isMacOs as jest.Mock).mockReturnValue(true); - const { getByTestId, queryByTestId } = render(); - - expect(getByTestId('cmd-button')).toBeInTheDocument(); - expect(queryByTestId('ctrl-button')).toBeNull(); - }); - - it('should render CtrlButton when isMacOs is false', () => { - (NavigatorHelper.isMacOs as jest.Mock).mockReturnValue(false); - const { getByTestId, queryByTestId } = render(); - - expect(getByTestId('ctrl-button')).toBeInTheDocument(); - expect(queryByTestId('cmd-button')).toBeNull(); - }); -}); diff --git a/openmetadata-ui/src/main/resources/ui/src/constants/constants.ts b/openmetadata-ui/src/main/resources/ui/src/constants/constants.ts index 029825f462d..d4dbf50dbd4 100644 --- a/openmetadata-ui/src/main/resources/ui/src/constants/constants.ts +++ b/openmetadata-ui/src/main/resources/ui/src/constants/constants.ts @@ -424,3 +424,6 @@ export const MAX_VISIBLE_OWNERS_FOR_FEED_TAB = 4; export const MAX_VISIBLE_OWNERS_FOR_FEED_CARD = 2; export const BREADCRUMB_SEPARATOR = '/'; + +export const VERSION_FETCH_TIME_KEY = 'versionFetchTime'; +export const ONE_HOUR_MS = 60 * 60 * 1000;