mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-11 09:51:30 +00:00
feat: notification visibility rules based on semver
This commit is contained in:
14
package-lock.json
generated
14
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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([]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user