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 (
- } + topElement={} /> ); 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; +}