diff --git a/package-lock.json b/package-lock.json index 4f5bb0ac9..8d63dedff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24408,7 +24408,7 @@ }, "packages/bruno-app": { "name": "@usebruno/app", - "version": "0.3.0", + "version": "1.39.0", "dependencies": { "@babel/preset-env": "^7.26.0", "@fontsource/inter": "^5.0.15", @@ -24468,6 +24468,7 @@ "react-redux": "^7.2.9", "react-tooltip": "^5.5.2", "sass": "^1.46.0", + "semver": "^7.7.1", "strip-json-comments": "^5.0.1", "styled-components": "^5.3.3", "system": "^2.0.1", @@ -24525,6 +24526,17 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "packages/bruno-app/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "packages/bruno-cli": { "name": "@usebruno/cli", "version": "1.16.0", diff --git a/packages/bruno-app/package.json b/packages/bruno-app/package.json index 56a2eab1a..f3e6fe292 100644 --- a/packages/bruno-app/package.json +++ b/packages/bruno-app/package.json @@ -1,6 +1,6 @@ { "name": "@usebruno/app", - "version": "0.3.0", + "version": "1.39.0", "private": true, "scripts": { "dev": "rsbuild dev", @@ -69,6 +69,7 @@ "react-redux": "^7.2.9", "react-tooltip": "^5.5.2", "sass": "^1.46.0", + "semver": "^7.7.1", "strip-json-comments": "^5.0.1", "styled-components": "^5.3.3", "system": "^2.0.1", diff --git a/packages/bruno-app/src/components/Notifications/index.js b/packages/bruno-app/src/components/Notifications/index.js index f308ca96a..653d56959 100644 --- a/packages/bruno-app/src/components/Notifications/index.js +++ b/packages/bruno-app/src/components/Notifications/index.js @@ -3,6 +3,7 @@ import { useState } from 'react'; import StyledWrapper from './StyleWrapper'; import Modal from 'components/Modal/index'; import { useEffect } from 'react'; +import { useApp } from 'providers/App'; import { fetchNotifications, markAllNotificationsAsRead, @@ -17,6 +18,7 @@ const PAGE_SIZE = 5; const Notifications = () => { const dispatch = useDispatch(); + const { version } = useApp(); const notifications = useSelector((state) => state.notifications.notifications); const [showNotificationsModal, setShowNotificationsModal] = useState(false); @@ -29,7 +31,9 @@ const Notifications = () => { const unreadNotifications = notifications.filter((notification) => !notification.read); useEffect(() => { - dispatch(fetchNotifications()); + dispatch(fetchNotifications({ + currentVersion: version + })); }, []); useEffect(() => { @@ -96,7 +100,9 @@ const Notifications = () => { { - dispatch(fetchNotifications()); + dispatch(fetchNotifications({ + currentVersion: version + })); setShowNotificationsModal(true); }} aria-label="Check all Notifications" diff --git a/packages/bruno-app/src/components/Sidebar/index.js b/packages/bruno-app/src/components/Sidebar/index.js index 50e19c22e..9f476e3c8 100644 --- a/packages/bruno-app/src/components/Sidebar/index.js +++ b/packages/bruno-app/src/components/Sidebar/index.js @@ -5,6 +5,7 @@ import Preferences from 'components/Preferences'; import Cookies from 'components/Cookies'; import ToolHint from 'components/ToolHint'; import GoldenEdition from './GoldenEdition'; +import { useApp } from 'providers/App'; import { useState, useEffect } from 'react'; import { useSelector, useDispatch } from 'react-redux'; @@ -20,7 +21,7 @@ const Sidebar = () => { const leftSidebarWidth = useSelector((state) => state.app.leftSidebarWidth); const preferencesOpen = useSelector((state) => state.app.showPreferences); const [goldenEditionOpen, setGoldenEditionOpen] = useState(false); - + const { version } = useApp(); const [asideWidth, setAsideWidth] = useState(leftSidebarWidth); const [cookiesOpen, setCookiesOpen] = useState(false); @@ -184,7 +185,7 @@ const Sidebar = () => { Star */} -
v1.36.1
+
v{version}
diff --git a/packages/bruno-app/src/providers/App/index.js b/packages/bruno-app/src/providers/App/index.js index 7664ae03e..b06d1d3a8 100644 --- a/packages/bruno-app/src/providers/App/index.js +++ b/packages/bruno-app/src/providers/App/index.js @@ -6,11 +6,12 @@ import ConfirmAppClose from './ConfirmAppClose'; import useIpcEvents from './useIpcEvents'; import useTelemetry from './useTelemetry'; import StyledWrapper from './StyledWrapper'; +import { version } from '../../../package.json'; export const AppContext = React.createContext(); export const AppProvider = (props) => { - useTelemetry(); + useTelemetry({ version }); useIpcEvents(); const dispatch = useDispatch(); @@ -37,7 +38,7 @@ export const AppProvider = (props) => { }, []); return ( - + {props.children} @@ -46,4 +47,12 @@ export const AppProvider = (props) => { ); }; +export const useApp = () => { + const context = React.useContext(AppContext); + if (!context) { + throw new Error('useApp must be used within an AppProvider'); + } + return context; +}; + export default AppProvider; diff --git a/packages/bruno-app/src/providers/App/useTelemetry.js b/packages/bruno-app/src/providers/App/useTelemetry.js index 6b64e1279..712a6efb7 100644 --- a/packages/bruno-app/src/providers/App/useTelemetry.js +++ b/packages/bruno-app/src/providers/App/useTelemetry.js @@ -42,7 +42,7 @@ const getAnonymousTrackingId = () => { return id; }; -const trackStart = () => { +const trackStart = (version) => { if (isPlaywrightTestRunning()) { return; } @@ -58,16 +58,18 @@ const trackStart = () => { event: 'start', properties: { os: platformLib.os.family, - version: '1.38.1' + version: version } }); }; -const useTelemetry = () => { +const useTelemetry = ({ version }) => { useEffect(() => { - trackStart(); - setInterval(trackStart, 24 * 60 * 60 * 1000); - }, []); + if (posthogApiKey && posthogApiKey.length) { + trackStart(version); + setInterval(trackStart, 24 * 60 * 60 * 1000); + } + }, [posthogApiKey]); }; export default useTelemetry; diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/notifications.js b/packages/bruno-app/src/providers/ReduxStore/slices/notifications.js index ca6c232d8..062f367ca 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/notifications.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/notifications.js @@ -1,7 +1,7 @@ import toast from 'react-hot-toast'; import { createSlice } from '@reduxjs/toolkit'; import { getAppInstallDate } from 'utils/common/platform'; - +import semver from 'semver'; const getReadNotificationIds = () => { try { let readNotificationIdsString = window.localStorage.getItem('bruno.notifications.read'); @@ -27,6 +27,26 @@ const initialState = { readNotificationIds: getReadNotificationIds() || [] }; +export const filterNotificationsByVersion = (notifications, currentVersion) => { + try { + if (!notifications) return []; + + if (!currentVersion) return notifications; + + return notifications.filter(notification => { + const { minVersion, maxVersion } = notification; + if (!minVersion && !maxVersion) return true; + if (!minVersion) return semver.lte(currentVersion, maxVersion); + if (!maxVersion) return semver.gte(currentVersion, minVersion); + + return semver.gte(currentVersion, minVersion) && semver.lte(currentVersion, maxVersion); + }); + } catch (error) { + console.error(error); + return []; + } +}; + export const notificationSlice = createSlice({ name: 'notifications', initialState, @@ -86,13 +106,14 @@ export const notificationSlice = createSlice({ export const { setNotifications, setFetchingStatus, markNotificationAsRead, markAllNotificationsAsRead } = notificationSlice.actions; -export const fetchNotifications = () => (dispatch, getState) => { +export const fetchNotifications = ({currentVersion}) => (dispatch, getState) => { return new Promise((resolve) => { const { ipcRenderer } = window; dispatch(setFetchingStatus(true)); ipcRenderer .invoke('renderer:fetch-notifications') .then((notifications) => { + notifications = filterNotificationsByVersion(notifications, currentVersion); dispatch(setNotifications({ notifications })); dispatch(setFetchingStatus(false)); resolve(notifications); diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/notifications.spec.js b/packages/bruno-app/src/providers/ReduxStore/slices/notifications.spec.js new file mode 100644 index 000000000..80e84d9bd --- /dev/null +++ b/packages/bruno-app/src/providers/ReduxStore/slices/notifications.spec.js @@ -0,0 +1,133 @@ +const { filterNotificationsByVersion } = require('./notifications'); + +describe('filterNotificationsByVersion - basic', () => { + it('should filter notifications by version', () => { + const notifications = [{ minVersion: '1.0.0', maxVersion: '1.1.0' }]; + const currentVersion = '1.0.5'; + const filteredNotifications = filterNotificationsByVersion(notifications, currentVersion); + expect(filteredNotifications).toEqual([{ minVersion: '1.0.0', maxVersion: '1.1.0' }]); + }); + + it('should gracefully handle no notifications', () => { + const notifications = []; + const currentVersion = '1.0.5'; + const filteredNotifications = filterNotificationsByVersion(notifications, currentVersion); + expect(filteredNotifications).toEqual([]); + }); + + it('should gracefully handle notifications are undefined', () => { + const notifications = undefined; + const currentVersion = '1.0.5'; + const filteredNotifications = filterNotificationsByVersion(notifications, currentVersion); + expect(filteredNotifications).toEqual([]); + }); + + it('should gracefully handle scenario when no current version is provided', () => { + const notifications = [{ minVersion: '1.0.0', maxVersion: '1.1.0' }]; + const filteredNotifications = filterNotificationsByVersion(notifications); + expect(filteredNotifications).toEqual(notifications); + }); + + it('should gracefully handle scenario minVersion is undefined', () => { + const notifications = [{ minVersion: undefined, maxVersion: '1.1.0' }]; + const currentVersion = '1.0.5'; + const filteredNotifications = filterNotificationsByVersion(notifications, currentVersion); + expect(filteredNotifications).toEqual(notifications); + }); + + it('should gracefully handle scenario maxVersion is undefined', () => { + const notifications = [{ minVersion: '1.0.0', maxVersion: undefined }]; + const currentVersion = '1.0.5'; + const filteredNotifications = filterNotificationsByVersion(notifications, currentVersion); + expect(filteredNotifications).toEqual(notifications); + }); + + it('should gracefully handle scenario minVersion and maxVersion are undefined', () => { + const notifications = [{ minVersion: undefined, maxVersion: undefined }]; + const currentVersion = '1.0.5'; + const filteredNotifications = filterNotificationsByVersion(notifications, currentVersion); + expect(filteredNotifications).toEqual(notifications); + }); +}); + +describe('filterNotificationsByVersion - semver', () => { + it('should filter out notifications outside version range', () => { + const notifications = [ + { minVersion: '1.0.0', maxVersion: '1.1.0' }, // should be included + { minVersion: '2.0.0', maxVersion: '2.1.0' }, // should be filtered out + { minVersion: '0.5.0', maxVersion: '0.9.0' } // should be filtered out + ]; + const currentVersion = '1.0.5'; + const filteredNotifications = filterNotificationsByVersion(notifications, currentVersion); + expect(filteredNotifications).toEqual([ + { minVersion: '1.0.0', maxVersion: '1.1.0' } + ]); + }); + + it('should handle mixed valid and invalid version ranges', () => { + const notifications = [ + { minVersion: '1.0.0', maxVersion: '2.0.0' }, // should be included + { minVersion: '3.0.0', maxVersion: '4.0.0' }, // should be filtered out + { minVersion: '1.5.0', maxVersion: '1.8.0' }, // should be included + { minVersion: '0.1.0', maxVersion: '0.5.0' } // should be filtered out + ]; + const currentVersion = '1.6.0'; + const filteredNotifications = filterNotificationsByVersion(notifications, currentVersion); + expect(filteredNotifications).toEqual([ + { minVersion: '1.0.0', maxVersion: '2.0.0' }, + { minVersion: '1.5.0', maxVersion: '1.8.0' } + ]); + }); + + it('should handle edge cases of version ranges', () => { + const notifications = [ + { minVersion: '1.0.0', maxVersion: '1.0.0' }, // should be included + { minVersion: '1.0.1', maxVersion: '2.0.0' }, // should be filtered out + { minVersion: '0.9.9', maxVersion: '1.0.0' } // should be included + ]; + const currentVersion = '1.0.0'; + const filteredNotifications = filterNotificationsByVersion(notifications, currentVersion); + expect(filteredNotifications).toEqual([ + { minVersion: '1.0.0', maxVersion: '1.0.0' }, + { minVersion: '0.9.9', maxVersion: '1.0.0' } + ]); + }); +}); + +describe('filterNotificationsByVersion - undefined version bounds', () => { + it('should include notifications when minVersion is undefined and current version is below maxVersion', () => { + const notifications = [ + { minVersion: undefined, maxVersion: '2.0.0' } + ]; + const currentVersion = '1.5.0'; + const filteredNotifications = filterNotificationsByVersion(notifications, currentVersion); + expect(filteredNotifications).toEqual(notifications); + }); + + it('should exclude notifications when minVersion is undefined and current version is above maxVersion', () => { + const notifications = [ + { minVersion: undefined, maxVersion: '2.0.0' } + ]; + const currentVersion = '2.1.0'; + const filteredNotifications = filterNotificationsByVersion(notifications, currentVersion); + expect(filteredNotifications).toEqual([]); + }); + + it('should include notifications when maxVersion is undefined and current version is above minVersion', () => { + const notifications = [ + { minVersion: '1.0.0', maxVersion: undefined } + ]; + const currentVersion = '2.0.0'; + const filteredNotifications = filterNotificationsByVersion(notifications, currentVersion); + expect(filteredNotifications).toEqual(notifications); + }); + + it('should exclude notifications when maxVersion is undefined and current version is below minVersion', () => { + const notifications = [ + { minVersion: '1.0.0', maxVersion: undefined } + ]; + const currentVersion = '0.9.0'; + const filteredNotifications = filterNotificationsByVersion(notifications, currentVersion); + expect(filteredNotifications).toEqual([]); + }); +}); \ No newline at end of file