Skip to content

Commit

Permalink
Merge pull request #2112 from opossum-tool/introduce-qa-mode
Browse files Browse the repository at this point in the history
Introduce QA mode
  • Loading branch information
benedikt-richter authored Oct 9, 2023
2 parents 365ba57 + 03df80f commit fa35ca2
Show file tree
Hide file tree
Showing 24 changed files with 221 additions and 15 deletions.
9 changes: 9 additions & 0 deletions USER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -375,3 +375,12 @@ In the audit view, an attributions can be marked as preferred, to indicate that
Only signals with a source marked as `isRelevantForPreference` can be preferred over. If no signal source has this flag set, then the feature is disabled.

To mark an attribution as preferred, choose an attribution in the audit view, and open the `...` context menu. From there, you can mark or unmark an attribution as preferred. When an attribution is marked as preferred, `preferred = true` is written to the .opossum file, and the origin IDs of all visible signals relevant for preference are written in the field `preferredOverOriginIds`. Preferred attributions are displayed with a star icon.

Note that you are only able to mark an attribution as preferred (or unmark if it was preferred beforehand) if you are in
"QA Mode". To enable this mode click the item "QA Mode" in the `View` submenu.

![disabled_qa_mode](./docs/user_guide_screenshots/disabled_qa_mode.png)

If "QA Mode" is enabled the icon will change as in the screenshot below.

![enabled_qa_mode](./docs/user_guide_screenshots/enabled_qa_mode.png)
Binary file added docs/user_guide_screenshots/disabled_qa_mode.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/user_guide_screenshots/enabled_qa_mode.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/assets/icons/check-box-black.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/assets/icons/check-box-blank-black.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/assets/icons/check-box-blank-white.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/assets/icons/check-box-white.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
40 changes: 40 additions & 0 deletions src/ElectronBackend/main/__tests__/iconHelpers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// 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 { Menu, MenuItem } from 'electron';
import { makeFirstIconVisibleAndSecondHidden } from '../iconHelpers';

jest.mock('electron', () => ({
Menu: {
setApplicationMenu: jest.fn(),
buildFromTemplate: jest.fn(),
getApplicationMenu: jest.fn(),
},
}));
jest.mock('electron-is-dev', () => false);

describe('makeFirstIconVisibleAndSecondHidden', () => {
let toBeVisibleMenuItem: Partial<MenuItem>;
let toBeHiddenMenuItem: Partial<MenuItem>;

it('should make the first item visible and the second item hidden', () => {
toBeVisibleMenuItem = { visible: false };
toBeHiddenMenuItem = { visible: true };
(Menu.getApplicationMenu as jest.Mock).mockImplementation(() => ({
getMenuItemById: jest.fn().mockImplementation((id) => {
if (id === 'toBeVisibleMenuItem') return toBeVisibleMenuItem;
if (id === 'toBeHiddenMenuItem') return toBeHiddenMenuItem;
}),
}));

makeFirstIconVisibleAndSecondHidden(
'toBeVisibleMenuItem',
'toBeHiddenMenuItem',
);

expect(toBeVisibleMenuItem.visible).toBe(true);
expect(toBeHiddenMenuItem.visible).toBe(false);
});
});
13 changes: 12 additions & 1 deletion src/ElectronBackend/main/iconHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import isDev from 'electron-is-dev';
import path from 'path';
import upath from 'upath';
import { getBasePathOfAssets } from './getPath';
import electron from 'electron';
import electron, { Menu } from 'electron';

export function getIconPath(): string {
const basePath = isDev
Expand All @@ -24,3 +24,14 @@ export function getIconBasedOnTheme(
? path.join(getBasePathOfAssets(), white_icon)
: path.join(getBasePathOfAssets(), black_icon);
}

export function makeFirstIconVisibleAndSecondHidden(
firstItemId: string,
secondItemId: string,
): void {
const itemToMakeVisible =
Menu.getApplicationMenu()?.getMenuItemById(firstItemId);
if (itemToMakeVisible) itemToMakeVisible.visible = true;
const itemToHide = Menu.getApplicationMenu()?.getMenuItemById(secondItemId);
if (itemToHide) itemToHide.visible = false;
}
42 changes: 41 additions & 1 deletion src/ElectronBackend/main/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,13 @@ import {
} from './notice-document-helpers';
import { ExportType } from '../../shared/shared-types';
import { isFileLoaded } from '../utils/getLoadedFile';
import { getIconBasedOnTheme } from './iconHelpers';
import {
getIconBasedOnTheme,
makeFirstIconVisibleAndSecondHidden,
} from './iconHelpers';
export function createMenu(mainWindow: BrowserWindow): Menu {
const webContents = mainWindow.webContents;

return Menu.buildFromTemplate([
{
label: 'File',
Expand Down Expand Up @@ -311,6 +315,42 @@ export function createMenu(mainWindow: BrowserWindow): Menu {
label: 'Zoom Out',
role: 'zoomOut',
},
{
icon: getIconBasedOnTheme(
'icons/check-box-blank-white.png',
'icons/check-box-blank-black.png',
),
label: 'QA Mode',
id: 'disabled-qa-mode',
click(): void {
makeFirstIconVisibleAndSecondHidden(
'enabled-qa-mode',
'disabled-qa-mode',
);
webContents.send(AllowedFrontendChannels.SetQAMode, {
qaMode: true,
});
},
visible: true,
},
{
icon: getIconBasedOnTheme(
'icons/check-box-white.png',
'icons/check-box-black.png',
),
label: 'QA Mode',
id: 'enabled-qa-mode',
click(): void {
makeFirstIconVisibleAndSecondHidden(
'disabled-qa-mode',
'enabled-qa-mode',
);
webContents.send(AllowedFrontendChannels.SetQAMode, {
qaMode: false,
});
},
visible: false,
},
],
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ import {
getTemporaryDisplayPackageInfo,
wereTemporaryDisplayPackageInfoModified,
} from '../../state/selectors/all-views-resource-selectors';
import { getSelectedView } from '../../state/selectors/view-selector';
import {
getQAMode,
getSelectedView,
} from '../../state/selectors/view-selector';
import {
getDisplayedPackage,
getResolvedExternalAttributions,
Expand Down Expand Up @@ -114,6 +117,7 @@ export function AttributionColumn(props: AttributionColumnProps): ReactElement {
const wasPreferredFieldChanged: boolean =
initialManualDisplayPackageInfo.preferred !==
temporaryDisplayPackageInfo.preferred;
const qaMode = useAppSelector(getQAMode);

const {
isLicenseTextShown,
Expand Down Expand Up @@ -150,7 +154,7 @@ export function AttributionColumn(props: AttributionColumnProps): ReactElement {
temporaryDisplayPackageInfo.preSelected,
),
targetAttributionIsExternalAttribution: false,
isPreferenceFeatureEnabled,
isPreferenceFeatureEnabled: isPreferenceFeatureEnabled && qaMode,
attributionIsPreferred: temporaryDisplayPackageInfo.preferred ?? false,
view,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ describe('getMergeButtonsDisplayState', () => {
});
});

it('activates UnmarkAsPreferredButton when attribution is not preferred', () => {
it('deactivates UnmarkAsPreferredButton when attribution is not preferred in QA mode', () => {
expect(
getMergeButtonsDisplayState({
attributionIdMarkedForReplacement: 'attr2',
Expand All @@ -239,7 +239,7 @@ describe('getMergeButtonsDisplayState', () => {
});
});

it('activates UnmarkAsPreferredButton when attribution is preferred', () => {
it('activates UnmarkAsPreferredButton when attribution is preferred in QA mode', () => {
expect(
getMergeButtonsDisplayState({
attributionIdMarkedForReplacement: 'attr2',
Expand Down Expand Up @@ -284,6 +284,29 @@ describe('getMergeButtonsDisplayState', () => {
hideUnmarkAsPreferredButton: true,
});
});

it('hides MarkAsPreferredButton & UnmarkAsPreferredButton when preference feature is disabled', () => {
expect(
getMergeButtonsDisplayState({
attributionIdMarkedForReplacement: 'attr2',
targetAttributionId: 'attr',
selectedAttributionId: 'attr',
packageInfoWereModified: false,
targetAttributionIsPreSelected: true,
targetAttributionIsExternalAttribution: false,
attributionIsPreferred: true,
view: View.Audit,
isPreferenceFeatureEnabled: false,
}),
).toStrictEqual({
hideMarkForReplacementButton: false,
hideUnmarkForReplacementButton: true,
hideReplaceMarkedByButton: false,
deactivateReplaceMarkedByButton: true,
hideMarkAsPreferredButton: true,
hideUnmarkAsPreferredButton: true,
});
});
});

describe('getSelectedManualAttributionIdForAuditView', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
FileSupportPopupArgs,
IsLoadingArgs,
ParsedFileContent,
QAModeArgs,
} from '../../../shared/shared-types';
import { PopupType } from '../../enums/enums';
import {
Expand All @@ -44,6 +45,7 @@ import { getFileWithChildrenCheck } from '../../util/is-file-with-children';
import {
openPopup,
setIsLoading,
setQAMode,
} from '../../state/actions/view-actions/view-actions';

export function BackendCommunication(): ReactElement | null {
Expand Down Expand Up @@ -280,6 +282,15 @@ export function BackendCommunication(): ReactElement | null {
}
}

function setQAModeListener(
event: IpcRendererEvent,
qaModeArgs: QAModeArgs,
): void {
if (qaModeArgs) {
dispatch(setQAMode(qaModeArgs.qaMode));
}
}

useIpcRenderer(AllowedFrontendChannels.FileLoaded, fileLoadedListener, [
dispatch,
]);
Expand Down Expand Up @@ -342,6 +353,9 @@ export function BackendCommunication(): ReactElement | null {
showUpdateAppPopupListener,
[dispatch],
);
useIpcRenderer(AllowedFrontendChannels.SetQAMode, setQAModeListener, [
dispatch,
]);

return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { Attributions, ExportType } from '../../../../shared/shared-types';
describe('BackendCommunication', () => {
it('renders an Open file icon', () => {
renderComponentWithStore(<BackendCommunication />);
const expectedNumberOfCalls = 13;
const expectedNumberOfCalls = 14;
expect(window.electronAPI.on).toHaveBeenCalledTimes(expectedNumberOfCalls);
expect(window.electronAPI.on).toHaveBeenCalledWith(
AllowedFrontendChannels.FileLoaded,
Expand Down Expand Up @@ -62,6 +62,10 @@ describe('BackendCommunication', () => {
AllowedFrontendChannels.ShowLocatorPopup,
expect.anything(),
);
expect(window.electronAPI.on).toHaveBeenCalledWith(
AllowedFrontendChannels.SetQAMode,
expect.anything(),
);
});

it('filters the correct BOM attributions', () => {
Expand Down
6 changes: 5 additions & 1 deletion src/Frontend/Components/TopBar/__tests__/TopBar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { setResources } from '../../../state/actions/resource-actions/all-views-
describe('TopBar', () => {
it('renders an Open file icon', () => {
const { store } = renderComponentWithStore(<TopBar />);
const totalNumberOfCalls = 13;
const totalNumberOfCalls = 14;
expect(window.electronAPI.on).toHaveBeenCalledTimes(totalNumberOfCalls);
expect(window.electronAPI.on).toHaveBeenCalledWith(
AllowedFrontendChannels.FileLoaded,
Expand Down Expand Up @@ -69,6 +69,10 @@ describe('TopBar', () => {
AllowedFrontendChannels.ShowLocatorPopup,
expect.anything(),
);
expect(window.electronAPI.on).toHaveBeenCalledWith(
AllowedFrontendChannels.SetQAMode,
expect.anything(),
);

fireEvent.click(screen.queryByLabelText('open file') as Element);
expect(store.getState().resourceState).toMatchObject(initialResourceState);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ import {
ResourcesToAttributions,
SaveFileArgs,
} from '../../../../shared/shared-types';
import { renderComponentWithStore } from '../../../test-helpers/render-component-with-store';
import {
createTestAppStore,
renderComponentWithStore,
} from '../../../test-helpers/render-component-with-store';
import { ButtonText, PackagePanelTitle } from '../../../enums/enums';
import {
clickAddIconOnCardInAttributionList,
Expand Down Expand Up @@ -59,6 +62,7 @@ import {
expectReplaceAttributionPopupIsNotShown,
expectReplaceAttributionPopupIsShown,
} from '../../../test-helpers/popup-test-helpers';
import { setQAMode } from '../../../state/actions/view-actions/view-actions';

describe('The App in Audit View', () => {
it('renders TopBar and no ResourceBrowser when no resource file has been loaded', () => {
Expand Down Expand Up @@ -575,7 +579,7 @@ describe('The App in Audit View', () => {
);
});

it('preferred button is shown and sets an attribution as preferred', () => {
it('preferred button is shown and sets an attribution as preferred if QA mode is enabled', () => {
function getExpectedSaveFileArgs(
preferred: boolean,
preferredOverOriginIds?: Array<string>,
Expand Down Expand Up @@ -644,7 +648,9 @@ describe('The App in Audit View', () => {
resourcesToExternalAttributions: testResourcesToExternalAttributions,
}),
);
renderComponentWithStore(<App />);
const testStore = createTestAppStore();
renderComponentWithStore(<App />, { store: testStore });
testStore.dispatch(setQAMode(true));
clickOnButton(screen, ButtonText.Close);

clickOnElementInResourceBrowser(screen, 'file');
Expand Down Expand Up @@ -673,7 +679,7 @@ describe('The App in Audit View', () => {
expect(window.electronAPI.saveFile).toHaveBeenCalledTimes(2);
});

it('after setting an attribution to preferred, global save is disabled', () => {
it('after setting an attribution to preferred in QA mode, global save is disabled', () => {
const testResources: Resources = {
file: 1,
other_file: 1,
Expand Down Expand Up @@ -705,7 +711,9 @@ describe('The App in Audit View', () => {
},
}),
);
renderComponentWithStore(<App />);
const testStore = createTestAppStore();
renderComponentWithStore(<App />, { store: testStore });
testStore.dispatch(setQAMode(true));
clickOnButton(screen, ButtonText.Close);

clickOnElementInResourceBrowser(screen, 'file');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
getIsLoading,
getOpenPopup,
getPopupAttributionId,
getQAMode,
getSelectedView,
getTargetView,
isAttributionViewSelected,
Expand All @@ -23,6 +24,7 @@ import {
openPopup,
resetViewState,
setIsLoading,
setQAMode,
setTargetView,
updateActiveFilters,
} from '../view-actions';
Expand Down Expand Up @@ -159,6 +161,15 @@ describe('view actions', () => {
testStore.dispatch(setIsLoading(false));
expect(getIsLoading(testStore.getState())).toBe(false);
});

it('sets and gets QA mode state', () => {
const testStore = createTestAppStore();
expect(getQAMode(testStore.getState())).toBe(false);
testStore.dispatch(setQAMode(true));
expect(getQAMode(testStore.getState())).toBe(true);
testStore.dispatch(setQAMode(false));
expect(getQAMode(testStore.getState())).toBe(false);
});
});

describe('popup actions', () => {
Expand Down
Loading

0 comments on commit fa35ca2

Please sign in to comment.