diff --git a/src/Components/SmartComponents/CVEs/CVEs.js b/src/Components/SmartComponents/CVEs/CVEs.js index 53057beb8..c2d4477fb 100644 --- a/src/Components/SmartComponents/CVEs/CVEs.js +++ b/src/Components/SmartComponents/CVEs/CVEs.js @@ -13,7 +13,6 @@ import { changeCveListParameters, fetchCveListByAccount, selectCve, - deselectAllCves, expandCve, clearCVEsStore } from '../../../Store/Actions/Actions'; @@ -37,7 +36,7 @@ export const CVEs = () => { const [isColumnModalOpen, setColumnModalOpen] = useState(false); const cveList = useSelector( - ({ CVEsStore }) => CVEsStore.cveList + ({ CVEsStore }) => CVEsStore.cveList ); const parameters = useSelector( ({ CVEsStore }) => CVEsStore.parameters @@ -94,12 +93,12 @@ export const CVEs = () => { }; const showBusinessRiskModal = (cvesList, goToFirstPage) => { - const { meta } = cves; + const { meta } = cves; setBusinessRiskModal(() => () => { - dispatch(deselectAllCves()); + dispatch(clearCVEsStore()); updateRef(goToFirstPage ? { ...meta, page: 1 } : meta, apply); }} /> @@ -107,12 +106,12 @@ export const CVEs = () => { }; const showStatusModal = (cvesList, goToFirstPage) => { - const { meta } = cves; + const { meta } = cves; setStatusModal(() => () => { - dispatch(deselectAllCves()); + dispatch(clearCVEsStore()); updateRef(goToFirstPage ? { ...meta, page: 1 } : meta, apply); }} /> @@ -164,7 +163,7 @@ export const CVEs = () => { ); } else { - return ; + return ; } }; diff --git a/src/Components/SmartComponents/Modals/CvePairStatusModal.js b/src/Components/SmartComponents/Modals/CvePairStatusModal.js index 12f2642cd..301871191 100644 --- a/src/Components/SmartComponents/Modals/CvePairStatusModal.js +++ b/src/Components/SmartComponents/Modals/CvePairStatusModal.js @@ -44,7 +44,7 @@ export const CvePairStatusModal = ({ cveList, updateRef, inventoryList, intl, ty switch (type) { case 'systemsExposed': { const sameAsOverall = inventoryList.every(item => - item.status_id === cveList[0].status_id && item.justification === cveList[0].justification + item.status_id === cveList[0]?.status_id && item.justification === cveList[0]?.justification ); if (sameAsOverall) { // overall is only one therefore they are also same to each other @@ -56,7 +56,7 @@ export const CvePairStatusModal = ({ cveList, updateRef, inventoryList, intl, ty case 'systemDetail': { const sameAsEachOther = cveList.every((item, _, arr) => - item.status_id === arr[0].status_id && item.justification === arr[0].justification + item.status_id === arr[0]?.status_id && item.justification === arr[0]?.justification ); const sameAsOverall = cveList.every(item => @@ -83,7 +83,7 @@ export const CvePairStatusModal = ({ cveList, updateRef, inventoryList, intl, ty else { const sameAsEachOther = inventoryList.every((item, _, arr) => item.status_id === arr[0].status_id); - return sameAsEachOther ? inventoryList[0].status_id : '0'; + return sameAsEachOther ? inventoryList[0]?.status_id : '0'; } } @@ -91,12 +91,12 @@ export const CvePairStatusModal = ({ cveList, updateRef, inventoryList, intl, ty if (isOverallChecked) { const sameOverallAsEachOther = cveList.every((item, _, arr) => item.cve_status_id === arr[0].cve_status_id); - return sameOverallAsEachOther ? cveList[0].cve_status_id : '0'; + return sameOverallAsEachOther ? cveList[0]?.cve_status_id : '0'; } else { const sameAsEachOther = cveList.every((item, _, arr) => item.status_id === arr[0].status_id); - return sameAsEachOther ? cveList[0].status_id : '0'; + return sameAsEachOther ? cveList[0]?.status_id : '0'; } } } @@ -110,24 +110,24 @@ export const CvePairStatusModal = ({ cveList, updateRef, inventoryList, intl, ty } else { const sameAsEachOther = inventoryList.every((item, _, arr) => - item.justification === arr[0].justification); + item.justification === arr[0]?.justification); - return sameAsEachOther ? inventoryList[0].justification || '' : ''; + return sameAsEachOther ? inventoryList[0]?.justification || '' : ''; } } case 'systemDetail': { if (isOverallChecked) { const sameOverallAsEachOther = cveList.every((item, _, arr) => - item.cve_justification === arr[0].cve_justification); + item.cve_justification === arr[0]?.cve_justification); - return sameOverallAsEachOther ? cveList[0].cve_justification || '' : ''; + return sameOverallAsEachOther ? cveList[0]?.cve_justification || '' : ''; } else { const sameAsEachOther = cveList.every((item, _, arr) => - item.justification === arr[0].justification); + item.justification === arr[0]?.justification); - return sameAsEachOther ? cveList[0].justification || '' : ''; + return sameAsEachOther ? cveList[0]?.justification || '' : ''; } } } @@ -136,11 +136,11 @@ export const CvePairStatusModal = ({ cveList, updateRef, inventoryList, intl, ty const showDifferentStatusesWarning = () => { switch (type) { case 'systemsExposed': { - return inventoryList.some((item, _, arr) => item.status_id !== arr[0].status_id); + return inventoryList.some((item, _, arr) => item.status_id !== arr[0]?.status_id); } case 'systemDetail': { - return cveList.some((item, _, arr) => item.status_id !== arr[0].status_id); + return cveList.some((item, _, arr) => item.status_id !== arr[0]?.status_id); } } }; @@ -186,7 +186,7 @@ export const CvePairStatusModal = ({ cveList, updateRef, inventoryList, intl, ty messages.cvePairStatusModalSelected, { cveCount: cveList.length || 0, - cveId: cveList[0].id, // only used when length is 1 + cveId: cveList[0]?.id, // only used when length is 1 systemCount: inventoryList.length || 0, systemName: inventoryNames[0], // only used when length is 1 b: (...chunks) => {chunks} // explicitly specifying what is wrapped should be bold diff --git a/src/Components/SmartComponents/Remediation/Remediation.js b/src/Components/SmartComponents/Remediation/Remediation.js index c31a6f26f..1a8fc688e 100644 --- a/src/Components/SmartComponents/Remediation/Remediation.js +++ b/src/Components/SmartComponents/Remediation/Remediation.js @@ -41,11 +41,11 @@ const Remediation = ({ cves, systems, manyRules, addNotification: dispatchNotifi if (!manyRules && systems?.length === 1) { const [systemID] = systems; - issues = cves.reduce((acc, { id: cveID, rules }) => { + issues = cves.reduce((acc, { id: cveID, attributes: { rule } }) => { let issue = baseIssueTemplate(cveID, systemID); - if (rules?.rule_id) { - issue.id = `${issue.id}:${rules.rule_id}`; + if (rule?.rule_id) { + issue.id = `${issue.id}:${rule.rule_id}`; } return [...acc, issue]; diff --git a/src/Components/SmartComponents/SystemCves/SystemCveTableToolbar.js b/src/Components/SmartComponents/SystemCves/SystemCveTableToolbar.js index be5b8c46c..8e5a5c505 100644 --- a/src/Components/SmartComponents/SystemCves/SystemCveTableToolbar.js +++ b/src/Components/SmartComponents/SystemCves/SystemCveTableToolbar.js @@ -31,7 +31,7 @@ const SystemCveToolbarWithContext = ({ entity, intl, context }) => { methods.openCves(isOpen, expandedRows, !isAllExpanded); }; - const { cves, parameters, methods, selectedCves, isAllExpanded, canEditStatus, canRemediate } = context; + const { cves, parameters, methods, selectedCves, selectedRowsRawData, isAllExpanded, canEditStatus, canRemediate } = context; const { filter, advisory } = parameters; const selectedCvesCount = canRemediate && ((selectedCves && selectedCves.length) || 0); @@ -67,8 +67,6 @@ const SystemCveToolbarWithContext = ({ entity, intl, context }) => { } ]; - const selectedCvesData = selectedCves.flatMap(item => cves.data.filter(cve => item === cve.id)); - return ( { onSetPage: (_event, page) => handleChangePage(_event, page, methods.apply), onPerPageSelect: (_event, perPage) => handleSetPageSize(_event, perPage, methods.apply) }} - dedicatedAction={(canRemediate && entity && )} + dedicatedAction={(canRemediate && entity && + )} actionsConfig={{ actions, dropdownProps: { ouiaId: 'toolbar-actions' } diff --git a/src/Components/SmartComponents/SystemCves/SystemCves.js b/src/Components/SmartComponents/SystemCves/SystemCves.js index 7ff4e4f9a..c0a340c51 100644 --- a/src/Components/SmartComponents/SystemCves/SystemCves.js +++ b/src/Components/SmartComponents/SystemCves/SystemCves.js @@ -48,6 +48,11 @@ export const SystemCVEs = ({ entity, intl, allowedCveActions, showHeaderLabel, s const selectedCves = useSelector( ({ SystemCvesStore }) => SystemCvesStore.selectedCves ); + + const selectedRowsRawData = useSelector( + ({ SystemCvesStore }) => SystemCvesStore.selectedRowsRawData || [] + ); + const expandedRows = useSelector( ({ SystemCvesStore }) => SystemCvesStore.expandedRows ); @@ -61,8 +66,13 @@ export const SystemCVEs = ({ entity, intl, allowedCveActions, showHeaderLabel, s const downloadReport = format => { const params = { ...parameters, system: entity.id }; - DownloadReport.exec(fetchCveListBySystem, params, format, 'system-cves', notification => dispatch( - addNotification(notification)), () => dispatch(clearNotifications())); + DownloadReport.exec( + fetchCveListBySystem, + params, + format, + 'system-cves', + notification => dispatch(addNotification(notification)), () => dispatch(clearNotifications()) + ); }; const processError = error => { @@ -76,7 +86,7 @@ export const SystemCVEs = ({ entity, intl, allowedCveActions, showHeaderLabel, s />; } else { - return ; + return ; } }; @@ -92,7 +102,7 @@ export const SystemCVEs = ({ entity, intl, allowedCveActions, showHeaderLabel, s } if (isFirstLoad) { - apply({ sort: '-public_date', ...urlParameters }); + apply({ sort: '-public_date', ...urlParameters }); setIsFirstLoad(false); } else { @@ -110,20 +120,33 @@ export const SystemCVEs = ({ entity, intl, allowedCveActions, showHeaderLabel, s }, [dispatch]); const showStatusModal = (cvesList, goToFirstPage) => { - let selectedCves = Array.from(cves.data.filter(cve => cvesList.some(element => element.id === cve.id))); - selectedCves = selectedCves.map(( - // eslint-disable-next-line camelcase - { id, cve_status_id, status_id, status_justification: justification, cve_status_justification: cve_justification }) => - ({ id, cve_status_id, status_id, justification, cve_justification })); // omit properties we don't need - - setStatusModal(() => () => - ( updateRef(goToFirstPage ? { ...cves.meta, page: 1 } : cves.meta, apply)} + let cveList = selectedRowsRawData.map(( + { + id, + attributes: + { + // eslint-disable-next-line camelcase + cve_status_id, + // eslint-disable-next-line camelcase + status_id, + status_text: justification, + // eslint-disable-next-line camelcase + cve_status_text: cve_justification + } + }) => ({ id, cve_status_id, status_id, justification, cve_justification })); + + setStatusModal(() => () => ( + { + dispatch(clearSystemCvesStore()); + updateRef(goToFirstPage ? { ...cves.meta, page: 1 } : cves.meta, apply); + }} + inventoryList={[{ id: entity.id, display_name: entity.display_name }]} type={'systemDetail'} - />) - ); + /> + )); }; const handleCveSelect = (iSelected, payload) => { @@ -141,6 +164,7 @@ export const SystemCVEs = ({ entity, intl, allowedCveActions, showHeaderLabel, s cves, parameters, selectedCves, + selectedRowsRawData, expandedRows, isAllExpanded, canRemediate, @@ -215,8 +239,8 @@ const TranslateSystemCves = ({ customItnlProvider, ...props }) => { return - + }} > + ; }; diff --git a/src/Components/SmartComponents/SystemCves/SystemCves.test.js b/src/Components/SmartComponents/SystemCves/SystemCves.test.js index 1801d173e..9ad744927 100644 --- a/src/Components/SmartComponents/SystemCves/SystemCves.test.js +++ b/src/Components/SmartComponents/SystemCves/SystemCves.test.js @@ -1,5 +1,4 @@ import { ConnectedSystemCves } from './SystemCves'; -import toJson from 'enzyme-to-json'; import { mountWithIntl } from '../../../Helpers/MiscHelper'; import { BrowserRouter as Router } from 'react-router-dom'; import configureStore from 'redux-mock-store'; @@ -21,7 +20,7 @@ jest.mock("../../../Helpers/DownloadReport", () => ({ let state = { ...initialState, - parameters: { }, + parameters: {}, cveList: { isLoading: false, payload: { @@ -59,16 +58,42 @@ let state = { } }, expandedRows: [], - selectedCves: [], + prevLoadedRows: [], + selectedCves: ['CVE-2019-6454'], + selectedRowsRawData: [{ + id: 'CVE-2019-6454', + attributes: { + business_risk: "Not Defined", + business_risk_id: 0, + business_risk_text: null, + cve_status_id: 2, + status_justification: "testhello", + cve_status_text: "testhello", + cvss2_score: null, + cvss3_score: "6.500", + description: "A new domain bypass", + impact: "Moderate", + public_date: "2020-06-09T17:00:00+00:00", + reporter: 1, + rule: null, + status: "On-Hold", + status_id: 2, + status_text: "testhello", + synopsis: "CVE-2020-0543", + systems_affected: 1, + advisories_list: ['adv-1', 'adv-2'], + known_exploit: false + } + }], unsupported: false, }; const customMiddleWare = store => next => action => { useSelector.mockImplementation(callback => { - return callback({ SystemCvesStore: state, AppStore: {popup: {}} }); + return callback({ SystemCvesStore: state, AppStore: { popup: {} } }); }); next(action); - } +} const mockStore = configureStore([customMiddleWare]); const store = mockStore(initialState); window.insights = { loadRemediations: jest.fn(), chrome: { getUserPermissions: jest.fn() } } @@ -77,18 +102,18 @@ let wrapper; beforeEach(() => { store.clearActions(); useSelector.mockImplementation(callback => { - return callback({ SystemCvesStore: initialState, AppStore: {popup: {}} }); + return callback({ SystemCvesStore: initialState, AppStore: { popup: {} } }); }); wrapper = mountWithIntl( - + - ); + ); }); afterEach(() => { @@ -118,7 +143,7 @@ describe('SystemCves', () => { const storeForErrors = mockStoreErrors(initialState); wrapperForErrors = mountWithIntl( - + { it('Should generate error', () => { const customMiddleWareErrors = store => next => action => { useSelector.mockImplementation(callback => { - return callback({ + return callback({ SystemCvesStore: { ...initialState, cveList: { isLoading: false, payload: { errors: true, meta: {}, data: [] } } - } + } }); }); next(action); } renderComponent(customMiddleWareErrors); - expect(wrapperForErrors.find('[id="error.reload"]')).toBeTruthy(); + expect(wrapperForErrors.find('[id="n.reload"]')).toBeTruthy(); }); it('Should generate EmptyVulnerabilityData', () => { const customMiddleWareErrors = store => next => action => { useSelector.mockImplementation(callback => { - return callback({ + return callback({ SystemCvesStore: { ...initialState, cveList: { isLoading: false, payload: { errors: { status: 404 }, meta: {}, data: [] } } - } + } }); }); next(action); @@ -160,35 +185,6 @@ describe('SystemCves', () => { expect(wrapperForErrors.find('[id="emptyState.noData"]')).toBeTruthy(); }); }); - - it('Should wrap with context provider of shape', () => { - const { context } = wrapper.find('SystemCvesTableWithContext').props(); - expect(context).toEqual( - { - cves: { - data: expect.any(Array), - meta: expect.any(Object), - isLoading: false, - errors: undefined - }, - parameters: {}, - selectedCves: [], - expandedRows: [], - isAllExpanded: false, - canRemediate: true, - canEditStatus: true, - methods: { - apply: expect.any(Function), - downloadReport: expect.any(Function), - selectCves: expect.any(Function), - fetchResource: expect.any(Function), - showStatusModal: expect.any(Function), - openCves: expect.any(Function), - setColumnModalOpen: expect.any(Function) - } - } - ); - }); it('Should dispatch selectCve action on selecting (an) entitiy/entities', () => { const { context } = wrapper.find('SystemCvesTableWithContext').props(); @@ -222,11 +218,11 @@ describe('SystemCves', () => { it('Should dispatch changeParameters action on apply method call with provided parameters', async () => { const { context } = wrapper.find('SystemCvesTableWithContext').props(); - context.methods.apply({ test: 'testString '}); + context.methods.apply({ test: 'testString ' }); const dispatchedActions = store.getActions(); const action = dispatchedActions.filter(item => item.type === 'CHANGE_SYSTEM_CVE_LIST_PARAMETERS'); - expect(action[1].payload).toEqual({ test: 'testString '}); + expect(action[1].payload).toEqual({ test: 'testString ' }); expect(action).toHaveLength(2); }); @@ -236,13 +232,13 @@ describe('SystemCves', () => { const dispatchedActions = store.getActions(); const action = dispatchedActions.filter(item => item.type === 'CHANGE_SYSTEM_CVE_LIST_PARAMETERS'); - expect(action[1].payload).toEqual({}); + expect(action[1].payload).toEqual({}); expect(action).toHaveLength(2); }); it('Should display status modal', () => { const { context } = wrapper.find('SystemCvesTableWithContext').props(); - act(() => context.methods.showStatusModal([{ id: 'CVE-2019-6454' }])); // id of selected CVE + act(() => context.methods.showStatusModal()); // id of selected CVE wrapper.update(); const modal = wrapper.find('CvePairStatusModal'); const { cveList } = modal.props(); @@ -259,7 +255,7 @@ describe('SystemCves', () => { }); it('Should detect if cveList has different status in status modal', () => { - state.cveList.payload.data = [ + state.cveList.payload.data = [ ...state.cveList.payload.data, { type: 'cve', diff --git a/src/Components/SmartComponents/SystemCves/SystemCvesTableToolbar.test.js b/src/Components/SmartComponents/SystemCves/SystemCvesTableToolbar.test.js index 66498913e..1a4c37906 100644 --- a/src/Components/SmartComponents/SystemCves/SystemCvesTableToolbar.test.js +++ b/src/Components/SmartComponents/SystemCves/SystemCvesTableToolbar.test.js @@ -9,7 +9,7 @@ import { initialState } from '../../../Store/Reducers/SystemCvesStore'; import { BrowserRouter as Router } from 'react-router-dom'; import { handleChangePage, - handleSetPageSize, + handleSetPageSize, removeFilters } from '../../../Helpers/TableToolbarHelper'; import { SYSTEM_DETAILS_HEADER } from '../../../Helpers/constants'; @@ -17,51 +17,75 @@ import { SYSTEM_DETAILS_HEADER } from '../../../Helpers/constants'; jest.mock('../../../Helpers/TableToolbarHelper', () => ( { - ...(jest.requireActual('../../../Helpers/TableToolbarHelper')), - handleChangePage: jest.fn(), - handleSetPageSize: jest.fn(), - removeFilters: jest.fn() + ...(jest.requireActual('../../../Helpers/TableToolbarHelper')), + handleChangePage: jest.fn(), + handleSetPageSize: jest.fn(), + removeFilters: jest.fn() } )); const mockContext = { cves: createCveListBySystem('CVE-2019-6454', { - payload: { - isLoading: false, - meta: { - test: 'test' - }, - data: [ - { - type: 'cve', - id: 'CVE-2019-6454', - attributes: { - business_risk: "Not Defined", - business_risk_id: 0, - business_risk_text: null, - cve_status_id: 2, - cve_status_text: "testhello", - cvss2_score: null, - cvss3_score: "6.500", - description: "A new domain bypass", - impact: "Moderate", - public_date: "2020-06-09T17:00:00+00:00", - reporter: 1, - rule: { id: 'testId', description: 'testDescription', summary: 'testSummary'}, - status: "On-Hold", - status_id: 2, - status_text: "testhello", - synopsis: "CVE-2020-0543", - systems_affected: 1 - } + payload: { + isLoading: false, + meta: { + test: 'test' + }, + data: [ + { + type: 'cve', + id: 'CVE-2019-6454', + attributes: { + business_risk: "Not Defined", + business_risk_id: 0, + business_risk_text: null, + cve_status_id: 2, + cve_status_text: "testhello", + cvss2_score: null, + cvss3_score: "6.500", + description: "A new domain bypass", + impact: "Moderate", + public_date: "2020-06-09T17:00:00+00:00", + reporter: 1, + rule: { id: 'testId', description: 'testDescription', summary: 'testSummary' }, + status: "On-Hold", + status_id: 2, + status_text: "testhello", + synopsis: "CVE-2020-0543", + systems_affected: 1 } - ] + } + ] - } + } }, SYSTEM_DETAILS_HEADER), - parameters: { filter: 'testFilter'}, + parameters: { filter: 'testFilter' }, selectedCves: ['CVE-2019-6454'], expandedRows: ['CVE-2019-6454'], + prevLoadedRows: [], + selectedRowsRawData: [{ + id: 'CVE-2019-6454', + attributes: { + business_risk: "Not Defined", + business_risk_id: 0, + business_risk_text: null, + cve_status_id: 2, + cve_status_text: "testhello", + cvss2_score: null, + cvss3_score: "6.500", + description: "A new domain bypass", + impact: "Moderate", + public_date: "2020-06-09T17:00:00+00:00", + reporter: 1, + rule: { id: 'testId', description: 'testDescription', summary: 'testSummary' }, + status: "On-Hold", + status_id: 2, + status_text: "testhello", + synopsis: "CVE-2020-0543", + systems_affected: 1 + } + }], + isAllExpanded: false, methods: { apply: jest.fn(), @@ -69,7 +93,7 @@ const mockContext = { selectCves: jest.fn(), showStatusModal: jest.fn(), openCves: jest.fn(), - fetchResource: jest.fn().mockReturnValue({ + fetchResource: jest.fn().mockReturnValue({ payload: Promise.resolve({ data: [{ id: 'testID' }] }) @@ -80,14 +104,14 @@ const mockContext = { window.insights = { loadRemediations: jest.fn(), chrome: { getUserPermissions: jest.fn() } } const customMiddleWare = store => next => action => { next(action); - } +} const mockStore = configureStore([customMiddleWare]); const store = mockStore(initialState); describe('SystemCvesTableToolbar', () => { - - const mountWithStore = (testContext = {}) => + + const mountWithStore = (testContext = {}) => mountWithIntl( @@ -98,7 +122,7 @@ describe('SystemCvesTableToolbar', () => { ); - const mountWithoutStore = (testContext) => + const mountWithoutStore = (testContext) => mountWithIntl( @@ -120,39 +144,39 @@ describe('SystemCvesTableToolbar', () => { tempWrapper.unmount(); testcontext = {}; }); - + describe('actionsConfig:', () => { it('Should openCves be called with testCve if isAllExpanded = false', () => { const wrapper = mountWithStore(); wrapper.find("Button").first().simulate("click"); expect(mockContext.methods.openCves).toHaveBeenCalledWith(true, ["CVE-2019-6454"], true); }); - + it('Should openCves be called with empty array if isAllExpanded = true', () => { - let tempContext = { ...mockContext, isAllExpanded: true }; + let tempContext = { ...mockContext, isAllExpanded: true }; const tempWrapper = mountWithStore(tempContext); tempWrapper.find("Button").first().simulate("click"); expect(tempContext.methods.openCves).toHaveBeenCalledWith(false, [], false); tempWrapper.unmount(); tempContext = {}; }); - + it('Should showStatusModal be called with item ID and status_id = 0', () => { const testContext = { ...mockContext, selectedCves: ['testSelectedItem'] }; - const wrapper = mountWithStore(testContext); + const wrapper = mountWithStore(testContext); const { actionsConfig: { actions } } = wrapper.find('PrimaryToolbar').props(); actions[1].onClick(); expect(mockContext.methods.showStatusModal).toHaveBeenCalledWith([{ id: 'testSelectedItem' }], [], true); }); }); - + describe('pagination:', () => { it('Should call handlePageChange on page change', () => { - const testContext = { ...mockContext, selectedCves: ['testSelectedItem'] }; - const wrapper = mountWithStore(testContext); - const { pagination: { onSetPage } } = wrapper.find('PrimaryToolbar').props(); - onSetPage(null, 2); - expect(handleChangePage).toHaveBeenCalledWith(null, 2, expect.any(Function)); + const testContext = { ...mockContext, selectedCves: ['testSelectedItem'] }; + const wrapper = mountWithStore(testContext); + const { pagination: { onSetPage } } = wrapper.find('PrimaryToolbar').props(); + onSetPage(null, 2); + expect(handleChangePage).toHaveBeenCalledWith(null, 2, expect.any(Function)); }); it('Should call handleSetPageSize on page size change', () => { @@ -161,16 +185,16 @@ describe('SystemCvesTableToolbar', () => { const { pagination: { onPerPageSelect } } = wrapper.find('PrimaryToolbar').props(); onPerPageSelect(null, 2); expect(handleSetPageSize).toHaveBeenCalledWith(null, 2, expect.any(Function)); - }); + }); }); describe('bulkSelect:', () => { it('Should handleOnCheckboxChange unselect all selected items', () => { - const testContext = { ...mockContext, selectedCves: ['testSelectedItem'] }; - const wrapper = mountWithStore(testContext); - const { bulkSelect: { onSelect } } = wrapper.find('PrimaryToolbar').props(); - onSelect(); - expect(mockContext.methods.selectCves).toHaveBeenCalledWith(false, []); + const testContext = { ...mockContext, selectedCves: ['testSelectedItem'] }; + const wrapper = mountWithStore(testContext); + const { bulkSelect: { onSelect } } = wrapper.find('PrimaryToolbar').props(); + onSelect(); + expect(mockContext.methods.selectCves).toHaveBeenCalledWith(false, []); }); it('Should handleOnCheckboxChange select all items', () => { const testContext = { ...mockContext, selectedCves: [] }; @@ -178,16 +202,16 @@ describe('SystemCvesTableToolbar', () => { const { bulkSelect: { onSelect } } = wrapper.find('PrimaryToolbar').props(); onSelect(); expect(mockContext.methods.selectCves).toHaveBeenCalled(); - }); + }); }); describe('activeFiltersConfig:', () => { it('Should call removeFilters with two parameters', () => { - const testContext = { ...mockContext, selectedCves: ['testSelectedItem'] }; - const wrapper = mountWithStore(testContext); - const { activeFiltersConfig: { onDelete } } = wrapper.find('PrimaryToolbar').props(); - onDelete(null, 'testitem'); - expect(removeFilters).toHaveBeenCalledWith('testitem', expect.any(Function)); + const testContext = { ...mockContext, selectedCves: ['testSelectedItem'] }; + const wrapper = mountWithStore(testContext); + const { activeFiltersConfig: { onDelete } } = wrapper.find('PrimaryToolbar').props(); + onDelete(null, 'testitem'); + expect(removeFilters).toHaveBeenCalledWith('testitem', expect.any(Function)); }); }); }); \ No newline at end of file diff --git a/src/Components/SmartComponents/SystemCves/__snapshots__/SystemCvesTableToolbar.test.js.snap b/src/Components/SmartComponents/SystemCves/__snapshots__/SystemCvesTableToolbar.test.js.snap index 2749e448a..ad5aa93b6 100644 --- a/src/Components/SmartComponents/SystemCves/__snapshots__/SystemCvesTableToolbar.test.js.snap +++ b/src/Components/SmartComponents/SystemCves/__snapshots__/SystemCvesTableToolbar.test.js.snap @@ -200,9 +200,38 @@ exports[`SystemCvesTableToolbar Should render without errors 1`] = ` "parameters": Object { "filter": "testFilter", }, + "prevLoadedRows": Array [], "selectedCves": Array [ "CVE-2019-6454", ], + "selectedRowsRawData": Array [ + Object { + "attributes": Object { + "business_risk": "Not Defined", + "business_risk_id": 0, + "business_risk_text": null, + "cve_status_id": 2, + "cve_status_text": "testhello", + "cvss2_score": null, + "cvss3_score": "6.500", + "description": "A new domain bypass", + "impact": "Moderate", + "public_date": "2020-06-09T17:00:00+00:00", + "reporter": 1, + "rule": Object { + "description": "testDescription", + "id": "testId", + "summary": "testSummary", + }, + "status": "On-Hold", + "status_id": 2, + "status_text": "testhello", + "synopsis": "CVE-2020-0543", + "systems_affected": 1, + }, + "id": "CVE-2019-6454", + }, + ], } } downloadReport={[Function]} diff --git a/src/Store/ActionTypes.js b/src/Store/ActionTypes.js index 1081af812..30086b58c 100644 --- a/src/Store/ActionTypes.js +++ b/src/Store/ActionTypes.js @@ -11,7 +11,6 @@ export const CHANGE_SYSTEM_CVE_STATUS = 'CHANGE_SYSTEM_CVE_STATUS'; export const CHANGE_EXPOSED_SYSTEMS_PARAMETERS = 'CHANGE_EXPOSED_SYSTEMS_PARAMETERS'; export const CHANGE_CVE_LIST_PARAMETERS = 'CHANGE_CVE_LIST_PARAMETERS'; export const SELECT_CVE = 'SELECT_CVE'; -export const DESELECT_ALL_CVES = 'DESELECT_ALL_CVES'; export const SELECT_SYSTEM_CVE = 'SELECT_SYSTEM_CVE'; export const EXPAND_CVE = 'EXPAND_CVE'; export const EXPAND_ROW = 'EXPAND_ROW'; //TODO unify it with EXPAND_CVE action diff --git a/src/Store/Actions/Actions.js b/src/Store/Actions/Actions.js index 5bc8aea0a..edd70d259 100644 --- a/src/Store/Actions/Actions.js +++ b/src/Store/Actions/Actions.js @@ -94,11 +94,6 @@ export const selectCve = apiProps => ({ payload: apiProps }); -export const deselectAllCves = () => ({ - type: ActionTypes.DESELECT_ALL_CVES, - payload: [] -}); - export const expandCve = apiProps => ({ type: ActionTypes.EXPAND_CVE, payload: apiProps diff --git a/src/Store/Reducers/CVEsStore.js b/src/Store/Reducers/CVEsStore.js index 03ff1ae09..f3d996252 100644 --- a/src/Store/Reducers/CVEsStore.js +++ b/src/Store/Reducers/CVEsStore.js @@ -81,24 +81,17 @@ export const CVEsStore = (state = initialState, action) => { }; } - case ActionTypes.DESELECT_ALL_CVES: - return { - ...newState, - selectedCves: [], - selectedRowsRawData: [] - }; - case ActionTypes.EXPAND_CVE: { if (Array.isArray(action.payload)) { const expandedRows = action.payload; const isAllExpanded = action.payload.length === 0 ? false : true; - return { ...newState, expandedRows, isAllExpanded }; + return { ...newState, expandedRows, isAllExpanded }; } const cveName = newState.cveList.payload.data[action.payload / 2].id; const expandedRows = newState.expandedRows.slice(); expandedRows.includes(cveName) && expandedRows.splice(expandedRows.indexOf(cveName), 1) - || expandedRows.push(cveName); + || expandedRows.push(cveName); return { ...newState, expandedRows }; } diff --git a/src/Store/Reducers/SystemCvesStore.js b/src/Store/Reducers/SystemCvesStore.js index ac6810745..2a1786a29 100644 --- a/src/Store/Reducers/SystemCvesStore.js +++ b/src/Store/Reducers/SystemCvesStore.js @@ -1,5 +1,6 @@ import { applyReducerHash } from '@redhat-cloud-services/frontend-components-utilities/ReducerRegistry'; import { FETCH_SYSTEM_CVE_LIST } from '../ActionTypes'; +import unionBy from 'lodash/unionBy'; export const initialState = { parameters: { @@ -14,7 +15,9 @@ export const initialState = { error: false }, expandedRows: [], + prevLoadedRows: [], selectedCves: [], + selectedRowsRawData: [], isAllExpanded: false }; @@ -50,6 +53,7 @@ function fulfilledVulnerabilities(state, action) { payload, isLoading: false }, + prevLoadedRows: unionBy(action.payload.data, state.prevLoadedRows, 'id'), ...state.isAllExpanded && { expandedRows: action.payload.data.map(({ id }) => ({ id, isOpen: true })) } }; } @@ -57,9 +61,12 @@ function fulfilledVulnerabilities(state, action) { return state; } +// #TODO refactor to use selectRows helper function selectEntity(state, action) { let newState = state; let selectedCves = newState.selectedCves.slice(); + let prevLoadedRows = [].concat(state.prevLoadedRows); + if (Array.isArray(action.payload)) { selectedCves = action.payload; } else { @@ -67,7 +74,13 @@ function selectEntity(state, action) { selectedCves.push(action.payload); } - return { ...newState, selectedCves }; + let selectedRowsRawData = prevLoadedRows.filter(({ id }) => selectedCves.includes(id)); + + return { + ...newState, + selectedCves, + selectedRowsRawData + }; } function expandCve(state, action) { @@ -76,10 +89,10 @@ function expandCve(state, action) { let expandedRows = newState.expandedRows.slice(); if (cves.length > 0) { - cves.map(cve =>{ + cves.map(cve => { const index = expandedRows.findIndex(element => element.id === cve); - if (index > -1) { expandedRows[index] = ({ id: cve, isOpen });} - else {expandedRows.push({ id: cve, isOpen });} + if (index > -1) { expandedRows[index] = ({ id: cve, isOpen }); } + else { expandedRows.push({ id: cve, isOpen }); } }); } else { @@ -98,6 +111,8 @@ function changeParameters(state, action) { function clearSystemCvesStore(state) { let newState = state; newState.selectedCves = []; + newState.prevLoadedRows = []; + newState.selectedRowsRawData = []; newState.expandedRows = []; newState.parameters = { page: 1, diff --git a/src/Store/Reducers/reducersHelper.js b/src/Store/Reducers/reducersHelper.js index ad5823fc6..3820f9593 100644 --- a/src/Store/Reducers/reducersHelper.js +++ b/src/Store/Reducers/reducersHelper.js @@ -50,14 +50,14 @@ export const isTimestampValid = (stateTimestamp, actionTimestamp) => actionTimes * @returns {Object} */ export const addOrRemoveItemFromObj = (targetObj, inputArr) => { - const inputObj = inputArr.reduce((obj, { id, selected }) => { + const inputObj = inputArr.reduce((obj, { id, selected }) => { if (selected === false) { delete targetObj[id]; } else { obj[id] = selected; } - return obj; + return obj; }, {});