feat: notification visibility rules based on semver

This commit is contained in:
Anoop M D
2025-02-14 17:36:16 +05:30
parent 200732bac5
commit f8b4a0b85b
8 changed files with 201 additions and 16 deletions

14
package-lock.json generated
View File

@@ -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",

View File

@@ -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",

View File

@@ -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 = () => {
<a
className="relative cursor-pointer"
onClick={() => {
dispatch(fetchNotifications());
dispatch(fetchNotifications({
currentVersion: version
}));
setShowNotificationsModal(true);
}}
aria-label="Check all Notifications"

View File

@@ -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
</GitHubButton> */}
</div>
<div className="flex flex-grow items-center justify-end text-xs mr-2">v1.36.1</div>
<div className="flex flex-grow items-center justify-end text-xs mr-2">v{version}</div>
</div>
</div>
</div>

View File

@@ -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 (
<AppContext.Provider {...props} value="appProvider">
<AppContext.Provider {...props} value={{ version }}>
<StyledWrapper>
<ConfirmAppClose />
{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;

View File

@@ -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;

View File

@@ -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);

View File

@@ -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([]);
});
});