diff --git a/src/Frontend/Components/AttributionList/AttributionList.tsx b/src/Frontend/Components/AttributionList/AttributionList.tsx
index 8040529a8..47ce0ebce 100644
--- a/src/Frontend/Components/AttributionList/AttributionList.tsx
+++ b/src/Frontend/Components/AttributionList/AttributionList.tsx
@@ -17,6 +17,8 @@ const useStyles = makeStyles({
marginLeft: 5,
display: 'flex',
alignItems: 'center',
+ flexWrap: 'wrap',
+ justifyContent: 'space-between',
},
});
diff --git a/src/Frontend/Components/AttributionView/AttributionView.tsx b/src/Frontend/Components/AttributionView/AttributionView.tsx
index 5165ace31..573f5f553 100644
--- a/src/Frontend/Components/AttributionView/AttributionView.tsx
+++ b/src/Frontend/Components/AttributionView/AttributionView.tsx
@@ -7,21 +7,20 @@ import makeStyles from '@mui/styles/makeStyles';
import React, { ReactElement } from 'react';
import { useAppDispatch, useAppSelector } from '../../state/hooks';
import { Attributions } from '../../../shared/shared-types';
-import { FilterType, PackagePanelTitle } from '../../enums/enums';
+import { PackagePanelTitle } from '../../enums/enums';
import { changeSelectedAttributionIdOrOpenUnsavedPopup } from '../../state/actions/popup-actions/popup-actions';
import {
getAttributionIdMarkedForReplacement,
getManualAttributions,
} from '../../state/selectors/all-views-resource-selectors';
import { getSelectedAttributionId } from '../../state/selectors/attribution-view-resource-selectors';
-import { provideFollowUpFilter } from '../../util/provide-follow-up-filter';
+import { useFilters } from '../../util/use-filters';
import { useWindowHeight } from '../../util/use-window-height';
import { AttributionDetailsViewer } from '../AttributionDetailsViewer/AttributionDetailsViewer';
import { AttributionList } from '../AttributionList/AttributionList';
-import { Checkbox } from '../Checkbox/Checkbox';
import { OpossumColors } from '../../shared-styles';
import { topBarHeight } from '../TopBar/TopBar';
-import { getActiveFilters } from '../../state/selectors/view-selector';
+import { FilterMultiSelect } from '../Filter/FilterMultiSelect';
const countAndSearchOffset = 119;
@@ -35,11 +34,6 @@ const useStyles = makeStyles({
width: '30%',
margin: 5,
},
- checkBox: {
- display: 'flex',
- alignItems: 'center',
- marginLeft: 'auto',
- },
});
export function AttributionView(): ReactElement {
@@ -52,26 +46,15 @@ export function AttributionView(): ReactElement {
const attributionIdMarkedForReplacement: string = useAppSelector(
getAttributionIdMarkedForReplacement
);
- const activeFilters = useAppSelector(getActiveFilters);
- const filterForFollowUp = activeFilters.has(FilterType.OnlyFollowUp);
-
- const { handleFilterChange, getFilteredAttributions } = provideFollowUpFilter(
- filterForFollowUp,
- dispatch
- );
function onCardClick(attributionId: string): void {
dispatch(changeSelectedAttributionIdOrOpenUnsavedPopup(attributionId));
}
- const filteredAttributions = getFilteredAttributions(attributions);
+ const filteredAttributions = useFilters(attributions);
const title = `${PackagePanelTitle.AllAttributions} (${
Object.keys(attributions).length
})`;
- const checkBoxLabel = `Show only follow-up (${
- Object.values(attributions).filter((attribution) => attribution.followUp)
- .length
- })`;
return (
@@ -83,14 +66,7 @@ export function AttributionView(): ReactElement {
className={classes.attributionList}
maxHeight={useWindowHeight() - topBarHeight - countAndSearchOffset}
title={title}
- topRightElement={
-
- }
+ topRightElement={
}
/>
diff --git a/src/Frontend/Components/AttributionView/__tests__/AttributionView.test.tsx b/src/Frontend/Components/AttributionView/__tests__/AttributionView.test.tsx
index 7aede6458..557ce489c 100644
--- a/src/Frontend/Components/AttributionView/__tests__/AttributionView.test.tsx
+++ b/src/Frontend/Components/AttributionView/__tests__/AttributionView.test.tsx
@@ -10,13 +10,14 @@ import {
FollowUp,
ResourcesToAttributions,
} from '../../../../shared/shared-types';
-import { View } from '../../../enums/enums';
+import { FilterType, View } from '../../../enums/enums';
import { loadFromFile } from '../../../state/actions/resource-actions/load-actions';
import { navigateToView } from '../../../state/actions/view-actions/view-actions';
import { renderComponentWithStore } from '../../../test-helpers/render-component-with-store';
import {
- clickOnCheckbox,
+ clickOnFilter,
getParsedInputFileEnrichedWithTestData,
+ openDropDown,
} from '../../../test-helpers/general-test-helpers';
import { AttributionView } from '../AttributionView';
import { IpcRenderer } from 'electron';
@@ -49,6 +50,7 @@ describe('The Attribution View', () => {
packageVersion: '1.0',
copyright: 'Copyright John Doe',
licenseText: 'Some license text',
+ firstParty: true,
};
testResourcesToManualAttributions['test resource'] = [testManualUuid];
testManualAttributions[testOtherManualUuid] = {
@@ -103,9 +105,61 @@ describe('The Attribution View', () => {
expect(screen.getByText('Test package, 1.0'));
expect(screen.getByText('Test other package, 2.0'));
- clickOnCheckbox(screen, 'Show only follow-up (1)');
+ openDropDown(screen);
+ clickOnFilter(screen, FilterType.OnlyFollowUp);
expect(screen.getByText('Test other package, 2.0'));
expect(screen.queryByText('Test package, 1.0')).toBe(null);
});
+
+ test('filters Only First Party', () => {
+ const { store } = renderComponentWithStore();
+ store.dispatch(
+ loadFromFile(
+ getParsedInputFileEnrichedWithTestData({
+ resources: { ['test resource']: 1 },
+ manualAttributions: testManualAttributions,
+ resourcesToManualAttributions: testResourcesToManualAttributions,
+ })
+ )
+ );
+ store.dispatch(navigateToView(View.Attribution));
+ expect(screen.getByText('All Attributions (2)'));
+ expect(screen.getByText('Test package, 1.0'));
+ expect(screen.getByText('Test other package, 2.0'));
+
+ openDropDown(screen);
+ clickOnFilter(screen, FilterType.OnlyFirstParty);
+
+ expect(screen.getByText('Test package, 1.0'));
+ expect(screen.queryByText('Test other package, 2.0')).toBe(null);
+ });
+
+ test('filters Only First Party and follow ups and then hide first party and follow ups', () => {
+ const { store } = renderComponentWithStore();
+ store.dispatch(
+ loadFromFile(
+ getParsedInputFileEnrichedWithTestData({
+ resources: { ['test resource']: 1 },
+ manualAttributions: testManualAttributions,
+ resourcesToManualAttributions: testResourcesToManualAttributions,
+ })
+ )
+ );
+ store.dispatch(navigateToView(View.Attribution));
+ expect(screen.getByText('All Attributions (2)'));
+ expect(screen.getByText('Test package, 1.0'));
+ expect(screen.getByText('Test other package, 2.0'));
+
+ openDropDown(screen);
+ clickOnFilter(screen, FilterType.OnlyFirstParty);
+ clickOnFilter(screen, FilterType.OnlyFollowUp);
+
+ expect(screen.queryByText('Test package, 1.0')).toBe(null);
+ expect(screen.queryByText('Test other package, 2.0')).toBe(null);
+
+ clickOnFilter(screen, FilterType.HideFirstParty);
+ expect(screen.getByText('Test other package, 2.0'));
+ expect(screen.queryByText('Test package, 1.0')).toBe(null);
+ });
});
diff --git a/src/Frontend/Components/Filter/FilterMultiSelect.tsx b/src/Frontend/Components/Filter/FilterMultiSelect.tsx
new file mode 100644
index 000000000..7029552dd
--- /dev/null
+++ b/src/Frontend/Components/Filter/FilterMultiSelect.tsx
@@ -0,0 +1,117 @@
+// SPDX-FileCopyrightText: Facebook, Inc. and its affiliates
+// SPDX-FileCopyrightText: TNG Technology Consulting GmbH
+//
+// SPDX-License-Identifier: Apache-2.0
+
+import React, { ReactElement } from 'react';
+import MuiFormControl from '@mui/material/FormControl';
+import MuiInputLabel from '@mui/material/InputLabel';
+import MuiMenuItem from '@mui/material/MenuItem';
+import MuiSelect from '@mui/material/Select';
+import MuiChip from '@mui/material/Chip';
+import MuiBox from '@mui/material/Box';
+import MuiOutlinedInput from '@mui/material/OutlinedInput';
+import { FilterType } from '../../enums/enums';
+import { useAppDispatch, useAppSelector } from '../../state/hooks';
+import { updateActiveFilters } from '../../state/actions/view-actions/view-actions';
+import { getActiveFilters } from '../../state/selectors/view-selector';
+import { makeStyles } from '@mui/styles';
+import { OpossumColors } from '../../shared-styles';
+
+const ITEM_HEIGHT = 48;
+const ITEM_PADDING_TOP = 8;
+const FILTERS = [
+ FilterType.OnlyFollowUp,
+ FilterType.OnlyFirstParty,
+ FilterType.HideFirstParty,
+];
+
+const useStyles = makeStyles({
+ dropDownForm: {
+ width: '170px',
+ margin: '12px 4px',
+ backgroundColor: OpossumColors.white,
+ '& fieldset': {
+ borderRadius: 0,
+ },
+ '& label': {
+ backgroundColor: OpossumColors.white,
+ padding: '1px 3px',
+ },
+ },
+ dropDownSelect: {
+ height: '65px',
+ },
+ dropDownBox: {
+ display: 'flex',
+ flexWrap: 'wrap',
+ gap: 0.5,
+ },
+});
+
+export function FilterMultiSelect(): ReactElement {
+ const classes = useStyles();
+ const dispatch = useAppDispatch();
+ const activeFilters = Array.from(useAppSelector(getActiveFilters));
+
+ const updateFilters = (filter: FilterType): void => {
+ dispatch(updateActiveFilters(filter));
+ };
+
+ function getMenuItems(): Array {
+ return FILTERS.map((filter) => (
+ {
+ updateFilters(event.currentTarget.textContent as FilterType);
+ }}
+ >
+ {filter}
+
+ ));
+ }
+
+ return (
+
+ Filter
+ }
+ renderValue={(selectedFilters): ReactElement => (
+
+ {selectedFilters.map((filter) => (
+ {
+ event.preventDefault();
+ event.stopPropagation();
+ }}
+ onDelete={(): void => {
+ updateFilters(filter);
+ }}
+ />
+ ))}
+
+ )}
+ MenuProps={{
+ PaperProps: {
+ style: {
+ maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
+ width: 170,
+ },
+ },
+ }}
+ >
+ {getMenuItems()}
+
+
+ );
+}
diff --git a/src/Frontend/Components/Filter/__tests__/FilterMultiSelect.test.tsx b/src/Frontend/Components/Filter/__tests__/FilterMultiSelect.test.tsx
new file mode 100644
index 000000000..bd6fd3f1f
--- /dev/null
+++ b/src/Frontend/Components/Filter/__tests__/FilterMultiSelect.test.tsx
@@ -0,0 +1,30 @@
+// SPDX-FileCopyrightText: Facebook, Inc. and its affiliates
+// SPDX-FileCopyrightText: TNG Technology Consulting GmbH
+//
+// SPDX-License-Identifier: Apache-2.0
+
+import { FilterMultiSelect } from '../FilterMultiSelect';
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import {
+ expectFilterIsShown,
+ openDropDown,
+} from '../../../test-helpers/general-test-helpers';
+import { FilterType } from '../../../enums/enums';
+import { Provider } from 'react-redux';
+import { createAppStore } from '../../../state/configure-store';
+
+describe('FilterMultiSelect', () => {
+ test('renders the filters in a dropdown', () => {
+ const store = createAppStore();
+ render(
+
+
+
+ );
+ openDropDown(screen);
+ expectFilterIsShown(screen, FilterType.OnlyFirstParty);
+ expectFilterIsShown(screen, FilterType.OnlyFollowUp);
+ expectFilterIsShown(screen, FilterType.HideFirstParty);
+ });
+});
diff --git a/src/Frontend/Components/ReportView/ReportView.tsx b/src/Frontend/Components/ReportView/ReportView.tsx
index 1bef10478..6d49d689d 100644
--- a/src/Frontend/Components/ReportView/ReportView.tsx
+++ b/src/Frontend/Components/ReportView/ReportView.tsx
@@ -11,7 +11,7 @@ import {
AttributionsToResources,
AttributionsWithResources,
} from '../../../shared/shared-types';
-import { FilterType, View } from '../../enums/enums';
+import { View } from '../../enums/enums';
import { changeSelectedAttributionIdOrOpenUnsavedPopup } from '../../state/actions/popup-actions/popup-actions';
import { navigateToView } from '../../state/actions/view-actions/view-actions';
import {
@@ -21,11 +21,10 @@ import {
getManualAttributionsToResources,
} from '../../state/selectors/all-views-resource-selectors';
import { getAttributionsWithResources } from '../../util/get-attributions-with-resources';
-import { provideFollowUpFilter } from '../../util/provide-follow-up-filter';
-import { Checkbox } from '../Checkbox/Checkbox';
+import { useFilters } from '../../util/use-filters';
import { Table } from '../Table/Table';
import { OpossumColors } from '../../shared-styles';
-import { getActiveFilters } from '../../state/selectors/view-selector';
+import { FilterMultiSelect } from '../Filter/FilterMultiSelect';
const useStyles = makeStyles({
root: {
@@ -33,10 +32,6 @@ const useStyles = makeStyles({
height: '100%',
backgroundColor: OpossumColors.lightestBlue,
},
- checkBox: {
- display: 'flex',
- alignItems: 'center',
- },
});
export function ReportView(): ReactElement {
@@ -47,15 +42,8 @@ export function ReportView(): ReactElement {
);
const frequentLicenseTexts = useAppSelector(getFrequentLicensesTexts);
const isFileWithChildren = useAppSelector(getIsFileWithChildren);
- const activeFilters = useAppSelector(getActiveFilters);
- const filterForFollowUp = activeFilters.has(FilterType.OnlyFollowUp);
const dispatch = useAppDispatch();
- const { handleFilterChange, getFilteredAttributions } = provideFollowUpFilter(
- filterForFollowUp,
- dispatch
- );
-
const attributionsWithResources = getAttributionsWithResources(
attributions,
attributionsToResources
@@ -97,29 +85,16 @@ export function ReportView(): ReactElement {
const attributionsWithResourcesIncludingLicenseTexts =
getAttributionsWithResourcesIncludingLicenseTexts();
- const checkBoxLabel = `Show only follow-up (${
- Object.values(attributions).filter((attribution) => attribution.followUp)
- .length
- })`;
return (
);
diff --git a/src/Frontend/Components/ReportView/__tests__/ReportView.test.tsx b/src/Frontend/Components/ReportView/__tests__/ReportView.test.tsx
index db7b3e460..ef798dda1 100644
--- a/src/Frontend/Components/ReportView/__tests__/ReportView.test.tsx
+++ b/src/Frontend/Components/ReportView/__tests__/ReportView.test.tsx
@@ -14,12 +14,14 @@ import {
} from '../../../../shared/shared-types';
import { renderComponentWithStore } from '../../../test-helpers/render-component-with-store';
import {
- clickOnCheckbox,
+ clickOnFilter,
getParsedInputFileEnrichedWithTestData,
+ openDropDown,
} from '../../../test-helpers/general-test-helpers';
import { ReportView } from '../ReportView';
import { loadFromFile } from '../../../state/actions/resource-actions/load-actions';
import { setFrequentLicences } from '../../../state/actions/resource-actions/all-views-simple-actions';
+import { FilterType } from '../../../enums/enums';
describe('The ReportView', () => {
const testResources: Resources = { ['test resource']: 1 };
@@ -34,6 +36,7 @@ describe('The ReportView', () => {
packageVersion: '1.0',
copyright: 'Copyright John Doe',
licenseName: 'MIT',
+ firstParty: true,
};
testResourcesToManualAttributions['test resource'] = [testManualUuid];
testManualAttributions[testOtherManualUuid] = {
@@ -86,31 +89,62 @@ describe('The ReportView', () => {
expect(screen.getByText('Test package'));
expect(screen.getByText('Test other package'));
- clickOnCheckbox(screen, 'Show only follow-up (1)');
+ openDropDown(screen);
+ clickOnFilter(screen, FilterType.OnlyFollowUp);
expect(screen.getByText('Test other package'));
expect(screen.queryByText('Test package')).toBe(null);
});
- test('filters Follow-ups such that nothing left still shows checkbox', () => {
+ test('filters only first party', () => {
const { store } = renderComponentWithStore();
store.dispatch(
loadFromFile(
getParsedInputFileEnrichedWithTestData({
resources: testResources,
- manualAttributions: {
- [testManualUuid]: testManualAttributions[testManualUuid],
- },
+ manualAttributions: testManualAttributions,
resourcesToManualAttributions: testResourcesToManualAttributions,
})
)
);
expect(screen.getByText('Test package'));
+ expect(screen.getByText('Test other package'));
- clickOnCheckbox(screen, 'Show only follow-up (0)');
- expect(screen.queryByText('Test package')).toBe(null);
+ openDropDown(screen);
+ clickOnFilter(screen, FilterType.OnlyFirstParty);
- clickOnCheckbox(screen, 'Show only follow-up (0)');
expect(screen.getByText('Test package'));
+ expect(screen.queryByText('Test other package')).toBe(null);
+
+ clickOnFilter(screen, FilterType.OnlyFirstParty);
+ expect(screen.getByText('Test package'));
+ expect(screen.getByText('Test other package'));
+ });
+
+ test('filters Only First Party and follow ups and then hide first party and follow ups', () => {
+ const { store } = renderComponentWithStore();
+ store.dispatch(
+ loadFromFile(
+ getParsedInputFileEnrichedWithTestData({
+ resources: testResources,
+ manualAttributions: testManualAttributions,
+ resourcesToManualAttributions: testResourcesToManualAttributions,
+ })
+ )
+ );
+ expect(screen.getByText('Test package'));
+ expect(screen.getByText('Test other package'));
+
+ openDropDown(screen);
+ clickOnFilter(screen, FilterType.OnlyFirstParty);
+ clickOnFilter(screen, FilterType.OnlyFollowUp);
+
+ expect(screen.queryByText('Test package')).toBe(null);
+ expect(screen.queryByText('Test other package')).toBe(null);
+
+ clickOnFilter(screen, FilterType.HideFirstParty);
+
+ expect(screen.getByText('Test other package'));
+ expect(screen.queryByText('Test package')).toBe(null);
});
});
diff --git a/src/Frontend/integration-tests/all-views-tests/__tests__/all-views.test.tsx b/src/Frontend/integration-tests/all-views-tests/__tests__/all-views.test.tsx
index caa8eb744..12e4504ed 100644
--- a/src/Frontend/integration-tests/all-views-tests/__tests__/all-views.test.tsx
+++ b/src/Frontend/integration-tests/all-views-tests/__tests__/all-views.test.tsx
@@ -7,15 +7,17 @@ import { App } from '../../../Components/App/App';
import {
clickOnButton,
clickOnCheckbox,
+ clickOnFilter,
EMPTY_PARSED_FILE_CONTENT,
goToView,
mockElectronIpcRendererOn,
+ openDropDown,
TEST_TIMEOUT,
} from '../../../test-helpers/general-test-helpers';
import { screen } from '@testing-library/react';
import { IpcChannel } from '../../../../shared/ipc-channels';
import { renderComponentWithStore } from '../../../test-helpers/render-component-with-store';
-import { ButtonText, View } from '../../../enums/enums';
+import { ButtonText, FilterType, View } from '../../../enums/enums';
import { IpcRenderer } from 'electron';
import { ParsedFileContent } from '../../../../shared/shared-types';
import React from 'react';
@@ -50,7 +52,7 @@ describe('The App integration', () => {
global.window.ipcRenderer = originalIpcRenderer;
});
- test('app persists follow-up filter when changing views', () => {
+ test('app persists filters when changing views', () => {
const mockChannelReturn: ParsedFileContent = {
...EMPTY_PARSED_FILE_CONTENT,
resources: {
@@ -81,6 +83,7 @@ describe('The App integration', () => {
documentConfidence: 90.0,
},
packageName: 'Vue',
+ firstParty: true,
},
},
resourcesToAttributions: {
@@ -98,14 +101,13 @@ describe('The App integration', () => {
screen.getByText('JQuery');
screen.getByText('Angular');
screen.getByText('Vue');
- screen.getByText('Show only follow-up (1)');
clickOnCardInAttributionList(screen, 'Vue');
clickOnCheckbox(screen, 'Follow-up');
clickOnButton(screen, ButtonText.Save);
- screen.getByText('Show only follow-up (2)');
- clickOnCheckbox(screen, 'Show only follow-up (2)');
+ openDropDown(screen);
+ clickOnFilter(screen, FilterType.OnlyFollowUp);
screen.getByText('JQuery');
expect(screen.queryByText('Angular')).toBeFalsy();
screen.getAllByText('Vue');
@@ -115,14 +117,20 @@ describe('The App integration', () => {
expect(screen.queryByText('Angular')).toBeFalsy();
screen.getByText('Vue');
- clickOnCheckbox(screen, 'Show only follow-up (2)');
+ openDropDown(screen);
+ clickOnFilter(screen, FilterType.OnlyFollowUp);
screen.getByText('JQuery');
screen.getByText('Angular');
screen.getByText('Vue');
+ clickOnFilter(screen, FilterType.OnlyFirstParty);
+ expect(screen.queryByText('JQuery')).toBeFalsy();
+ expect(screen.queryByText('Angular')).toBeFalsy();
+ screen.getByText('Vue');
+
goToView(screen, View.Attribution);
- screen.getByText('JQuery');
- screen.getByText('Angular');
- screen.getAllByText('Vue');
+ expect(screen.queryByText('JQuery')).toBeFalsy();
+ expect(screen.queryByText('Angular')).toBeFalsy();
+ screen.getByText('Vue');
});
});
diff --git a/src/Frontend/test-helpers/general-test-helpers.ts b/src/Frontend/test-helpers/general-test-helpers.ts
index adefbb93e..9b0647f52 100644
--- a/src/Frontend/test-helpers/general-test-helpers.ts
+++ b/src/Frontend/test-helpers/general-test-helpers.ts
@@ -182,6 +182,24 @@ export function clickOnCheckbox(screen: Screen, label: string): void {
);
}
+export function clickOnDeleteIcon(screen: Screen): void {
+ fireEvent.click(screen.getByTestId('CancelIcon') as Element);
+}
+
+export function openDropDown(screen: Screen): void {
+ fireEvent.mouseDown(
+ screen.getByTestId('test-id-filter-multi-select').childNodes[0] as Element
+ );
+}
+
+export function expectFilterIsShown(screen: Screen, label: string): void {
+ expect(screen.getByLabelText(label)).toBeTruthy();
+}
+
+export function clickOnFilter(screen: Screen, label: string): void {
+ fireEvent.click(screen.getByLabelText(label) as Element);
+}
+
export function expectElementsInAutoCompleteAndSelectFirst(
screen: Screen,
elements: Array
diff --git a/src/Frontend/util/__tests__/provide-follow-up-filter.test.tsx b/src/Frontend/util/__tests__/provide-follow-up-filter.test.tsx
deleted file mode 100644
index 78b09ddc9..000000000
--- a/src/Frontend/util/__tests__/provide-follow-up-filter.test.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-// SPDX-FileCopyrightText: Facebook, Inc. and its affiliates
-// SPDX-FileCopyrightText: TNG Technology Consulting GmbH
-//
-// SPDX-License-Identifier: Apache-2.0
-
-import { Attributions, FollowUp } from '../../../shared/shared-types';
-import { provideFollowUpFilter } from '../provide-follow-up-filter';
-import { createTestAppStore } from '../../test-helpers/render-component-with-store';
-
-describe('useFollowUpFilter', () => {
- const testManualUuid = 'a32f2f96-f40e-11ea-adc1-0242ac120002';
- const testOtherManualUuid = 'a32f2f96-f40e-11ea-adc1-0242ac120003';
- const testManualAttributions: Attributions = {};
- testManualAttributions[testManualUuid] = {
- attributionConfidence: 0,
- comment: 'Some comment',
- packageName: 'Test package',
- packageVersion: '1.0',
- copyright: 'Copyright John Doe',
- licenseText: 'Some license text',
- };
- testManualAttributions[testOtherManualUuid] = {
- attributionConfidence: 0,
- comment: 'Some other comment',
- packageName: 'Test other package',
- packageVersion: '2.0',
- copyright: 'other Copyright John Doe',
- licenseText: 'Some other license text',
- followUp: FollowUp,
- };
-
- test('returns working getFilteredAttributions with follow-up filter', () => {
- const store = createTestAppStore();
- const result = provideFollowUpFilter(true, store.dispatch);
- const filteredAttributions = result.getFilteredAttributions(
- testManualAttributions
- );
- expect(filteredAttributions).toEqual({
- [testOtherManualUuid]: testManualAttributions[testOtherManualUuid],
- });
- });
-
- test('returns working getFilteredAttributions without filter', () => {
- const store = createTestAppStore();
- const result = provideFollowUpFilter(false, store.dispatch);
- const filteredAttributions = result.getFilteredAttributions(
- testManualAttributions
- );
- expect(filteredAttributions).toBe(testManualAttributions);
- });
-});
diff --git a/src/Frontend/util/__tests__/use-filters.test.tsx b/src/Frontend/util/__tests__/use-filters.test.tsx
new file mode 100644
index 000000000..c7cc554f8
--- /dev/null
+++ b/src/Frontend/util/__tests__/use-filters.test.tsx
@@ -0,0 +1,116 @@
+// SPDX-FileCopyrightText: Facebook, Inc. and its affiliates
+// SPDX-FileCopyrightText: TNG Technology Consulting GmbH
+//
+// SPDX-License-Identifier: Apache-2.0
+
+import React, { ReactElement } from 'react';
+import { Attributions, FollowUp } from '../../../shared/shared-types';
+import { useFilters } from '../use-filters';
+import {
+ createTestAppStore,
+ renderComponentWithStore,
+} from '../../test-helpers/render-component-with-store';
+import { updateActiveFilters } from '../../state/actions/view-actions/view-actions';
+import { FilterType } from '../../enums/enums';
+
+let filteredAttributions: Attributions;
+
+function TestComponent(props: {
+ manualAttributions: Attributions;
+}): ReactElement {
+ filteredAttributions = useFilters(props.manualAttributions);
+ return ;
+}
+
+describe('useFollowUpFilter', () => {
+ const testManualUuid = 'a32f2f96-f40e-11ea-adc1-0242ac120002';
+ const testOtherManualUuid = 'a32f2f96-f40e-11ea-adc1-0242ac120003';
+ const testManualAttributions: Attributions = {};
+ testManualAttributions[testManualUuid] = {
+ attributionConfidence: 0,
+ comment: 'Some comment',
+ packageName: 'Test package',
+ packageVersion: '1.0',
+ copyright: 'Copyright John Doe',
+ licenseText: 'Some license text',
+ firstParty: true,
+ };
+ testManualAttributions[testOtherManualUuid] = {
+ attributionConfidence: 0,
+ comment: 'Some other comment',
+ packageName: 'Test other package',
+ packageVersion: '2.0',
+ copyright: 'other Copyright John Doe',
+ licenseText: 'Some other license text',
+ followUp: FollowUp,
+ };
+
+ test('returns working getFilteredAttributions with follow-up filter', () => {
+ const store = createTestAppStore();
+ store.dispatch(updateActiveFilters(FilterType.OnlyFollowUp));
+ renderComponentWithStore(
+ ,
+ { store }
+ );
+ expect(filteredAttributions).toEqual({
+ [testOtherManualUuid]: testManualAttributions[testOtherManualUuid],
+ });
+ });
+
+ test('returns working getFilteredAttributions without filter', () => {
+ const store = createTestAppStore();
+ renderComponentWithStore(
+ ,
+ { store }
+ );
+ expect(filteredAttributions).toBe(testManualAttributions);
+ });
+
+ test('returns working getFilteredAttributions with only first party filter', () => {
+ const store = createTestAppStore();
+ store.dispatch(updateActiveFilters(FilterType.OnlyFirstParty));
+ renderComponentWithStore(
+ ,
+ { store }
+ );
+ expect(filteredAttributions).toEqual({
+ [testManualUuid]: testManualAttributions[testManualUuid],
+ });
+ });
+
+ test('returns working getFilteredAttributions with hide first party filter', () => {
+ const store = createTestAppStore();
+ store.dispatch(updateActiveFilters(FilterType.HideFirstParty));
+ renderComponentWithStore(
+ ,
+ { store }
+ );
+ expect(filteredAttributions).toEqual({
+ [testOtherManualUuid]: testManualAttributions[testOtherManualUuid],
+ });
+ });
+
+ test('returns working getFilteredAttributions with only first party and follow up filter', () => {
+ const store = createTestAppStore();
+ store.dispatch(updateActiveFilters(FilterType.OnlyFirstParty));
+ store.dispatch(updateActiveFilters(FilterType.OnlyFollowUp));
+ renderComponentWithStore(
+ ,
+ { store }
+ );
+ expect(filteredAttributions).toEqual({});
+ });
+
+ test('returns working getFilteredAttributions with hide first party and follow up filter', () => {
+ const store = createTestAppStore();
+ store.dispatch(updateActiveFilters(FilterType.HideFirstParty));
+ store.dispatch(updateActiveFilters(FilterType.OnlyFollowUp));
+ renderComponentWithStore(
+ ,
+ { store }
+ );
+ expect(filteredAttributions).toEqual({
+ [testOtherManualUuid]: testManualAttributions[testOtherManualUuid],
+ });
+ });
+});
diff --git a/src/Frontend/util/provide-follow-up-filter.ts b/src/Frontend/util/provide-follow-up-filter.ts
deleted file mode 100644
index b812e59aa..000000000
--- a/src/Frontend/util/provide-follow-up-filter.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-// SPDX-FileCopyrightText: Facebook, Inc. and its affiliates
-// SPDX-FileCopyrightText: TNG Technology Consulting GmbH
-//
-// SPDX-License-Identifier: Apache-2.0
-
-import pickBy from 'lodash/pickBy';
-import {
- Attributions,
- AttributionsWithResources,
- PackageInfo,
-} from '../../shared/shared-types';
-import { FilterType } from '../enums/enums';
-import { updateActiveFilters } from '../state/actions/view-actions/view-actions';
-import { AppThunkDispatch } from '../state/types';
-
-export function provideFollowUpFilter(
- filterForFollowUp: boolean,
- dispatch: AppThunkDispatch
-): {
- handleFilterChange: () => void;
- getFilteredAttributions(
- attributions: AttributionsWithResources | Attributions
- ): AttributionsWithResources | Attributions;
-} {
- function handleFilterChange(): void {
- dispatch(updateActiveFilters(FilterType.OnlyFollowUp));
- }
- function getFilteredAttributions(
- attributions: AttributionsWithResources | Attributions
- ): AttributionsWithResources | Attributions {
- return filterForFollowUp
- ? pickBy(attributions, (value: PackageInfo) => value.followUp)
- : attributions;
- }
-
- return { handleFilterChange, getFilteredAttributions };
-}
diff --git a/src/Frontend/util/use-filters.ts b/src/Frontend/util/use-filters.ts
new file mode 100644
index 000000000..4895d1b7e
--- /dev/null
+++ b/src/Frontend/util/use-filters.ts
@@ -0,0 +1,35 @@
+// SPDX-FileCopyrightText: Facebook, Inc. and its affiliates
+// SPDX-FileCopyrightText: TNG Technology Consulting GmbH
+//
+// SPDX-License-Identifier: Apache-2.0
+
+import pickBy from 'lodash/pickBy';
+import {
+ Attributions,
+ AttributionsWithResources,
+ PackageInfo,
+} from '../../shared/shared-types';
+import { FilterType } from '../enums/enums';
+import { useAppSelector } from '../state/hooks';
+import { getActiveFilters } from '../state/selectors/view-selector';
+
+export function useFilters(
+ attributions: AttributionsWithResources
+): AttributionsWithResources;
+export function useFilters(attributions: Attributions): Attributions;
+export function useFilters(
+ attributions: AttributionsWithResources | Attributions
+): AttributionsWithResources | Attributions {
+ const activeFilters = useAppSelector(getActiveFilters);
+
+ attributions = activeFilters.has(FilterType.OnlyFollowUp)
+ ? pickBy(attributions, (value: PackageInfo) => value.followUp)
+ : attributions;
+ attributions = activeFilters.has(FilterType.OnlyFirstParty)
+ ? pickBy(attributions, (value: PackageInfo) => value.firstParty)
+ : attributions;
+ attributions = activeFilters.has(FilterType.HideFirstParty)
+ ? pickBy(attributions, (value: PackageInfo) => !value.firstParty)
+ : attributions;
+ return attributions;
+}