Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chore/user config handling and performance fixes #2856

Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
7c6cc6f
chore: proper default handling removing necessity for most of the nulls
Hellgartner Mar 14, 2025
b16240a
test: ad test for properly setting default values
Hellgartner Mar 14, 2025
194a1ee
chore: add reducer for user config
Hellgartner Mar 18, 2025
b725fc8
chore: sync user settings on app start
Hellgartner Mar 18, 2025
bc4dee4
chore: keep state in sync with backend
Hellgartner Mar 18, 2025
93def01
chore: move show classifications and show criticality to new approach
Hellgartner Mar 18, 2025
769c623
chore: move the remaining usage of use user settings to the new way
Hellgartner Mar 18, 2025
0f3855f
test: fix e2e tests
Hellgartner Mar 19, 2025
2e33471
fix: fix updating multiple values
Hellgartner Mar 19, 2025
86a4850
test: add tests for user settings actions
Hellgartner Mar 19, 2025
25861ef
test: add e2e test
Hellgartner Mar 19, 2025
9c3af15
fix: review-comment: add a few empty lines to improve readability
Hellgartner Mar 20, 2025
5393b42
fix: review-comment: Move showing the project popup to the new mechanism
Hellgartner Mar 20, 2025
27149ef
fix: review-comment: Simplify IPC interface
Hellgartner Mar 20, 2025
ad933a3
Merge branch 'feat/make-classification-and-criticality-optional' into…
Hellgartner Mar 20, 2025
f03015d
refactor: review-column: rename UserSettingsProvider
Hellgartner Mar 20, 2025
8fd911b
refactor: review-comment: make update user settings accept a update f…
Hellgartner Mar 20, 2025
8314a57
refactor: review-comment: user the new capability to inline the toggl…
Hellgartner Mar 20, 2025
3cd7899
feat: respect settings for showing also in overview table
Hellgartner Mar 19, 2025
7b9d4d4
feat: handle case when sorted by column is disabled
Hellgartner Mar 19, 2025
fa3605e
test: review-comment: Improve test
Hellgartner Mar 20, 2025
eb8308d
fix: review-comment: fix corner case
Hellgartner Mar 20, 2025
7870d15
fix: revert accidentally committed code
Hellgartner Mar 24, 2025
9e776e7
fix: review-comment: move expect back to test to avoid linter warnings
Hellgartner Mar 24, 2025
3645445
fix: review-comment: move to one unified useUserSettings
Hellgartner Mar 24, 2025
457aaeb
fix: review-comment: typing and naming improvements
Hellgartner Mar 24, 2025
371186a
refactor: review-comment: preparation - assimilate set and updating f…
Hellgartner Mar 24, 2025
76bbf53
refactor: review-comment: preparation - use update everywhere
Hellgartner Mar 24, 2025
c194ea6
refactor: review-comment: preparation - unify set and update
Hellgartner Mar 24, 2025
01e9d38
refactor: review-comment: use update everywhere
Hellgartner Mar 25, 2025
3c8b452
Merge pull request #2861 from opossum-tool/feat/toggle-also-statistic…
Hellgartner Mar 25, 2025
e9f59ec
fix: merge followup
Hellgartner Mar 25, 2025
d60e209
Merge branch 'feat/make-classification-and-criticality-optional' into…
Hellgartner Mar 25, 2025
1922977
refactor: review-comment: Improve naming
Hellgartner Mar 25, 2025
664e0f8
refactor: review-comment: Inline function to improve typing
Hellgartner Mar 25, 2025
4750eaf
refactor: review-comments: extract type to avoid duplication
Hellgartner Mar 25, 2025
a90c984
Merge branch 'feat/introduce_license_classification' into chore/user-…
Hellgartner Mar 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/ElectronBackend/main/__tests__/menu.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import electron, { BrowserWindow, MenuItemConstructorOptions } from 'electron';

import { createMenu } from '../menu';
import { UserSettings } from '../user-settings';
import { UserSettingsProvider } from '../user-settings-provider';

jest.mock('electron', () => ({
BrowserWindow: class BrowserWindowMock {},
Expand Down Expand Up @@ -48,7 +48,7 @@ describe('create menu', () => {
];
testCases.forEach((testCase) => {
it(`evaluates ${testCase.darkMode ? 'dark' : 'light'} mode properly`, async () => {
await UserSettings.init();
await UserSettingsProvider.init();
const mainWindow = new BrowserWindow();

// Important to set this up only here and not in the mock setup
Expand Down
149 changes: 149 additions & 0 deletions src/ElectronBackend/main/__tests__/user-settings-provider.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// SPDX-FileCopyrightText: Meta Platforms, Inc. and its affiliates
// SPDX-FileCopyrightText: TNG Technology Consulting GmbH <https://www.tngtech.com>
//
// SPDX-License-Identifier: Apache-2.0
import { BrowserWindow } from 'electron';
import settings from 'electron-settings';
import { rmSync } from 'node:fs';
import { mkdtemp } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';

import { AllowedFrontendChannels } from '../../../shared/ipc-channels';
import {
DEFAULT_PANEL_SIZES,
DEFAULT_USER_SETTINGS,
} from '../../../shared/shared-constants';
import { UserSettingsProvider } from '../user-settings-provider';

type MockedBrowserWindow = BrowserWindow & {
sendFunction: (channel: string, ...args: Array<unknown>) => void;
};

jest.mock('electron', () => {
const sendFunction = jest.fn();
return {
BrowserWindow: {
getAllWindows: () => {
return [
{
webContents: {
send: sendFunction,
},
},
];
},
sendFunction,
},
};
});
describe('UserSettings', () => {
let temporaryDir: string | undefined = undefined;
beforeEach(async () => {
temporaryDir = await mkdtemp(join(tmpdir(), 'OpossumUiTesting-'));
settings.configure({ dir: temporaryDir, atomicSave: true });
});

afterEach(() => {
if (temporaryDir) {
rmSync(temporaryDir, { recursive: true, force: true });
}
});

describe('init', () => {
it('sets up the default values if empty', async () => {
await UserSettingsProvider.init();

const result = await settings.get();

expect(result).toEqual(DEFAULT_USER_SETTINGS);
});

it('overwrites only the non set values if there are already values set', async () => {
await settings.set('qaMode', true);

await UserSettingsProvider.init();

const result = await settings.get();

expect(result).toEqual({ ...DEFAULT_USER_SETTINGS, qaMode: true });
});

it('resets everything if reset is requested', async () => {
const oldEnvironment = process.env;
process.env = { ...oldEnvironment, RESET: 'TRUE' };

await settings.set('qaMode', true);

await UserSettingsProvider.init();

const result = await settings.get();

expect(result).toEqual({ ...DEFAULT_USER_SETTINGS });
process.env = oldEnvironment;
});
});
describe('get', () => {
it('gets a user setting from a predescribed path', async () => {
await UserSettingsProvider.init();

const panelSizes = await UserSettingsProvider.get('panelSizes');

expect(panelSizes).toEqual(DEFAULT_PANEL_SIZES);
});

it('gets the full user settings', async () => {
await UserSettingsProvider.init();

const userSettings = await UserSettingsProvider.get();

expect(userSettings).toEqual(DEFAULT_USER_SETTINGS);
});
});

describe('write operations', () => {
it('sets a value and communicates to the frontend', async () => {
await UserSettingsProvider.set('qaMode', true);

const qaMode = await UserSettingsProvider.get('qaMode');
expect(qaMode).toBe(true);
expect(
(BrowserWindow as unknown as MockedBrowserWindow).sendFunction,
).toHaveBeenCalledWith(AllowedFrontendChannels.UserSettingsChanged, {
qaMode: true,
});
});

it('sets a value and does not communicates to the frontend if disabled', async () => {
await UserSettingsProvider.set('qaMode', true, {
skipNotification: true,
});

const qaMode = await UserSettingsProvider.get('qaMode');
expect(qaMode).toBe(true);
expect(
(BrowserWindow as unknown as MockedBrowserWindow).sendFunction,
).not.toHaveBeenCalled();
});

it('allows to update multiple values at once', async () => {
await UserSettingsProvider.init();

await UserSettingsProvider.update({
qaMode: true,
showClassifications: false,
});

const userSettings = await UserSettingsProvider.get();

expect(
(BrowserWindow as unknown as MockedBrowserWindow).sendFunction,
).toHaveBeenCalledTimes(2);
expect(userSettings).toEqual({
...DEFAULT_USER_SETTINGS,
qaMode: true,
showClassifications: false,
});
});
});
});
16 changes: 11 additions & 5 deletions src/ElectronBackend/main/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { dialog, ipcMain, Menu, systemPreferences } from 'electron';
import os from 'os';

import { AllowedFrontendChannels, IpcChannel } from '../../shared/ipc-channels';
import { UserSettings } from '../../shared/shared-types';
import { getMessageBoxContentForErrorsWrapper } from '../errorHandling/errorHandling';
import { createWindow, loadWebApp } from './createWindow';
import {
Expand All @@ -21,7 +22,7 @@ import {
import { createMenu } from './menu';
import { DisabledMenuItemHandler } from './menu/DisabledMenuItemHandler';
import { openFileFromCliOrEnvVariableIfProvided } from './openFileFromCliOrEnvVariableIfProvided';
import { UserSettings } from './user-settings';
import { UserSettingsProvider } from './user-settings-provider';

export async function main(): Promise<void> {
try {
Expand All @@ -35,7 +36,7 @@ export async function main(): Promise<void> {

const mainWindow = createWindow();

await UserSettings.init();
await UserSettingsProvider.init();
Menu.setApplicationMenu(await createMenu(mainWindow));

mainWindow.webContents.session.webRequest.onBeforeSendHeaders(
Expand Down Expand Up @@ -93,10 +94,15 @@ export async function main(): Promise<void> {
);
ipcMain.handle(IpcChannel.OpenLink, openLinkListener);
ipcMain.handle(IpcChannel.GetUserSettings, (_, key) =>
UserSettings.get(key),
UserSettingsProvider.get(key),
);
ipcMain.handle(IpcChannel.SetUserSettings, (_, { key, value }) =>
UserSettings.set(key, value, { skipNotification: true }),
ipcMain.handle(IpcChannel.GetFullUserSettings, () =>
UserSettingsProvider.get(),
);
ipcMain.handle(
IpcChannel.SetUserSettings,
(_, userSettings: Partial<UserSettings>) =>
UserSettingsProvider.update(userSettings, true),
);

await loadWebApp(mainWindow);
Expand Down
17 changes: 10 additions & 7 deletions src/ElectronBackend/main/menu/viewMenu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { MenuItemConstructorOptions } from 'electron';

import { text } from '../../../shared/text';
import { getIconBasedOnTheme } from '../iconHelpers';
import { UserSettings } from '../user-settings';
import { UserSettingsProvider } from '../user-settings-provider';
import { switchableMenuItem } from './switchableMenuItem';

function getShowDevTools(): MenuItemConstructorOptions {
Expand Down Expand Up @@ -61,7 +61,7 @@ function getShowClassifications(
id: 'show-classifications',
label: text.menu.viewSubmenu.showClassifications,
onToggle: (newState: boolean) =>
UserSettings.set('showClassifications', newState),
UserSettingsProvider.set('showClassifications', newState),
});
}

Expand All @@ -72,22 +72,25 @@ function getShowCriticality(
id: 'show-criticality',
label: text.menu.viewSubmenu.showCriticality,
onToggle: (newState: boolean) =>
UserSettings.set('showCriticality', newState),
UserSettingsProvider.set('showCriticality', newState),
});
}

function getQaMode(qaMode: boolean | null): Array<MenuItemConstructorOptions> {
return switchableMenuItem(qaMode, {
id: 'qa-mode',
label: text.menu.viewSubmenu.qaMode,
onToggle: (newState: boolean) => UserSettings.set('qaMode', newState),
onToggle: (newState: boolean) =>
UserSettingsProvider.set('qaMode', newState),
});
}

export async function getViewMenu(): Promise<MenuItemConstructorOptions> {
const qaMode = await UserSettings.get('qaMode');
const showCriticality = await UserSettings.get('showCriticality');
const showClassifications = await UserSettings.get('showClassifications');
const qaMode = await UserSettingsProvider.get('qaMode');
const showCriticality = await UserSettingsProvider.get('showCriticality');
const showClassifications = await UserSettingsProvider.get(
'showClassifications',
);
return {
label: text.menu.view,
submenu: [
Expand Down
74 changes: 74 additions & 0 deletions src/ElectronBackend/main/user-settings-provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// SPDX-FileCopyrightText: Meta Platforms, Inc. and its affiliates
// SPDX-FileCopyrightText: TNG Technology Consulting GmbH <https://www.tngtech.com>
//
// SPDX-License-Identifier: Apache-2.0
import { BrowserWindow } from 'electron';
import log from 'electron-log';
import settings from 'electron-settings';

import { AllowedFrontendChannels } from '../../shared/ipc-channels';
import { DEFAULT_USER_SETTINGS } from '../../shared/shared-constants';
import {
UserSettings as IUserSettings,
UserSettings,
} from '../../shared/shared-types';

export class UserSettingsProvider {
public static async init() {
if (process.argv.includes('--reset') || process.env.RESET) {
log.info('Resetting user settings');
await settings.set(DEFAULT_USER_SETTINGS as unknown as never);
} else {
const currentSettings = await settings.get();
await settings.set({
...DEFAULT_USER_SETTINGS,
...currentSettings,
});
}
}

public static get<T extends keyof IUserSettings>(
path: T,
): Promise<IUserSettings[T]>;
public static get(): Promise<IUserSettings>;
public static get<T extends keyof IUserSettings>(
path?: T,
): Promise<IUserSettings[T]> | Promise<IUserSettings> {
if (path) {
return settings.get(path) as Promise<IUserSettings[T]>;
}
return settings.get() as unknown as Promise<IUserSettings>;
}

public static async update(
userSettings: Partial<IUserSettings>,
skipNotification: boolean = false,
): Promise<void> {
for (const key of Object.keys(userSettings)) {
const properKey = key as keyof UserSettings;
if (userSettings[properKey] !== undefined) {
await UserSettingsProvider.set(properKey, userSettings[properKey], {
skipNotification,
});
}
}
}

public static async set<T extends keyof IUserSettings>(
path: T,
value: IUserSettings[T],
{ skipNotification }: { skipNotification?: boolean } = {},
): Promise<void> {
await settings.set(path, value);

if (!skipNotification) {
const partialSettingsToUpdate = Object.fromEntries([[path, value]]);
BrowserWindow.getAllWindows().forEach((window) => {
window.webContents.send(
AllowedFrontendChannels.UserSettingsChanged,
partialSettingsToUpdate,
);
});
}
}
}
46 changes: 0 additions & 46 deletions src/ElectronBackend/main/user-settings.ts

This file was deleted.

Loading
Loading