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;