diff --git a/src/components/i18n/__tests__/__snapshots__/i18n.test.js.snap b/src/components/i18n/__tests__/__snapshots__/i18n.test.js.snap index 59bff1cc7..85130bc50 100644 --- a/src/components/i18n/__tests__/__snapshots__/i18n.test.js.snap +++ b/src/components/i18n/__tests__/__snapshots__/i18n.test.js.snap @@ -157,11 +157,11 @@ exports[`I18n Component should generate a predictable locale key output snapshot "keys": [ { "key": "curiosity-inventory.header", - "match": "translate('curiosity-inventory.header', { context: id })", + "match": "translate('curiosity-inventory.header', { context: [id, productId] })", }, { "key": "curiosity-inventory.header", - "match": "translate('curiosity-inventory.header', { context: key })", + "match": "translate('curiosity-inventory.header', { context: [key, productId] })", }, ], }, diff --git a/src/components/inventoryCard/__tests__/__snapshots__/inventoryCard.test.js.snap b/src/components/inventoryCard/__tests__/__snapshots__/inventoryCard.test.js.snap index 283314030..efa1624aa 100644 --- a/src/components/inventoryCard/__tests__/__snapshots__/inventoryCard.test.js.snap +++ b/src/components/inventoryCard/__tests__/__snapshots__/inventoryCard.test.js.snap @@ -1,109 +1,100 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`InventoryCard Component should handle expandable guests data: number of guests 1`] = ` - +{ + "ariaLabel": null, + "borders": true, + "children": null, + "className": "curiosity-inventory-list", + "columnHeaders": [ + "t(curiosity-inventory.header, {"context":"lorem"})", + "t(curiosity-inventory.header, {"context":"dolor"})", + "t(curiosity-inventory.header, {"context":"numberOfGuests"})", + ], + "isHeader": true, + "rows": [ + { + "cells": [ + "sit", + "amet", + 1, + ], + "expandedContent": undefined, + }, + ], + "summary": null, + "t": [Function], + "variant": "compact", +} `; exports[`InventoryCard Component should handle expandable guests data: number of guests, and id 1`] = ` -
, - }, - ] - } - summary={null} - t={[Function]} - variant="compact" -/> +{ + "ariaLabel": null, + "borders": true, + "children": null, + "className": "curiosity-inventory-list", + "columnHeaders": [ + "t(curiosity-inventory.header, {"context":"lorem"})", + "t(curiosity-inventory.header, {"context":"dolor"})", + "t(curiosity-inventory.header, {"context":"numberOfGuests"})", + "t(curiosity-inventory.header, {"context":"subscriptionManagerId"})", + ], + "isHeader": true, + "rows": [ + { + "cells": [ + "sit", + "amet", + 1, + "loremIpsum", + ], + "expandedContent": , + }, + ], + "summary": null, + "t": [Function], + "variant": "compact", +} `; exports[`InventoryCard Component should handle expandable guests data: number of guests, id, and NO expandable guests display 1`] = ` -
+{ + "ariaLabel": null, + "borders": true, + "children": null, + "className": "curiosity-inventory-list", + "columnHeaders": [ + "t(curiosity-inventory.header, {"context":"lorem"})", + "t(curiosity-inventory.header, {"context":"dolor"})", + "t(curiosity-inventory.header, {"context":"numberOfGuests"})", + "t(curiosity-inventory.header, {"context":"subscriptionManagerId"})", + ], + "isHeader": true, + "rows": [ + { + "cells": [ + "sit", + "amet", + 2, + "loremIpsum", + ], + "expandedContent": undefined, + }, + ], + "summary": null, + "t": [Function], + "variant": "compact", +} `; exports[`InventoryCard Component should handle multiple display states, error, pending, fulfilled: error 1`] = ` @@ -254,31 +245,9 @@ exports[`InventoryCard Component should handle multiple display states, error, p ariaLabel={null} borders={true} className="curiosity-inventory-list" - columnHeaders={ - [ - "t(curiosity-inventory.header, {"context":"lorem"})", - "t(curiosity-inventory.header, {"context":"dolor"})", - ] - } + columnHeaders={[]} isHeader={true} - rows={ - [ - { - "cells": [ - "ipsum", - "sit", - ], - "expandedContent": undefined, - }, - { - "cells": [ - "sit", - "amet", - ], - "expandedContent": undefined, - }, - ] - } + rows={[]} summary={null} t={[Function]} variant="compact" @@ -417,239 +386,73 @@ exports[`InventoryCard Component should handle multiple display states, error, p `; exports[`InventoryCard Component should handle variations in data: filtered data 1`] = ` - - - - - - - - - - - - - -
-
- - - - - - - - - - - +{ + "ariaLabel": null, + "borders": true, + "children": null, + "className": "curiosity-inventory-list", + "columnHeaders": [ + { + "title": "t(curiosity-inventory.header, {"context":"lorem"})", + "transforms": [], + }, + ], + "isHeader": true, + "rows": [ + { + "cells": [ + { + "title": "ipsum", + }, + ], + "expandedContent": undefined, + }, + { + "cells": [ + { + "title": "sit", + }, + ], + "expandedContent": undefined, + }, + ], + "summary": null, + "t": [Function], + "variant": "compact", +} `; exports[`InventoryCard Component should handle variations in data: variable data 1`] = ` - - - - - - - - - - - - - -
-
- - - - - - - - - - - +{ + "ariaLabel": null, + "borders": true, + "children": null, + "className": "curiosity-inventory-list", + "columnHeaders": [ + "t(curiosity-inventory.header, {"context":"lorem"})", + "t(curiosity-inventory.header, {"context":"dolor"})", + ], + "isHeader": true, + "rows": [ + { + "cells": [ + "ipsum", + "sit", + ], + "expandedContent": undefined, + }, + { + "cells": [ + "sit", + "amet", + ], + "expandedContent": undefined, + }, + ], + "summary": null, + "t": [Function], + "variant": "compact", +} `; exports[`InventoryCard Component should render a basic component: basic render 1`] = ` diff --git a/src/components/inventoryCard/__tests__/__snapshots__/inventoryCardHosts.test.js.snap b/src/components/inventoryCard/__tests__/__snapshots__/inventoryCardHosts.test.js.snap index ab82cbce9..43533bb13 100644 --- a/src/components/inventoryCard/__tests__/__snapshots__/inventoryCardHosts.test.js.snap +++ b/src/components/inventoryCard/__tests__/__snapshots__/inventoryCardHosts.test.js.snap @@ -18,6 +18,7 @@ exports[`InventoryCardHosts Component should render a basic component: basic ren useGetInventory={[Function]} useOnColumnSort={[Function]} useOnPage={[Function]} + useProduct={[Function]} useProductInventoryConfig={[Function]} useProductInventoryQuery={[Function]} useSession={[Function]} diff --git a/src/components/inventoryCard/__tests__/inventoryCard.test.js b/src/components/inventoryCard/__tests__/inventoryCard.test.js index 2f0acbc0f..8ab228474 100644 --- a/src/components/inventoryCard/__tests__/inventoryCard.test.js +++ b/src/components/inventoryCard/__tests__/inventoryCard.test.js @@ -109,14 +109,15 @@ describe('InventoryCard Component', () => { }) }; - const component = await shallowHookComponent(); - expect(component).toMatchSnapshot('variable data'); + const component = await mountHookComponent(); + expect(component.find(Table).props()).toMatchSnapshot('variable data'); component.setProps({ useProductInventoryConfig: () => ({ filters: [{ id: 'lorem' }] }) }); - expect(component).toMatchSnapshot('filtered data'); + component.update(); + expect(component.find(Table).props()).toMatchSnapshot('filtered data'); }); it('should handle expandable guests data', async () => { @@ -137,8 +138,8 @@ describe('InventoryCard Component', () => { }) }; - const component = await shallowHookComponent(); - expect(component.find(Table)).toMatchSnapshot('number of guests'); + const component = await mountHookComponent(); + expect(component.find(Table).props()).toMatchSnapshot('number of guests'); component.setProps({ ...props, @@ -153,7 +154,8 @@ describe('InventoryCard Component', () => { }) }); - expect(component.find(Table)).toMatchSnapshot('number of guests, and id'); + component.update(); + expect(component.find(Table).props()).toMatchSnapshot('number of guests, and id'); component.setProps({ ...props, @@ -176,6 +178,7 @@ describe('InventoryCard Component', () => { }) }); - expect(component.find(Table)).toMatchSnapshot('number of guests, id, and NO expandable guests display'); + component.update(); + expect(component.find(Table).props()).toMatchSnapshot('number of guests, id, and NO expandable guests display'); }); }); diff --git a/src/components/inventoryCard/inventoryCard.js b/src/components/inventoryCard/inventoryCard.js index e76dfca9d..371a5ce32 100644 --- a/src/components/inventoryCard/inventoryCard.js +++ b/src/components/inventoryCard/inventoryCard.js @@ -1,15 +1,20 @@ -import React from 'react'; +import React, { useState } from 'react'; import PropTypes from 'prop-types'; +import { useDeepCompareEffect } from 'react-use'; import { TableVariant } from '@patternfly/react-table'; import { Bullseye, Card, CardActions, CardBody, CardFooter, CardHeader, CardHeaderMain } from '@patternfly/react-core'; import { TableToolbar } from '@redhat-cloud-services/frontend-components/TableToolbar'; import { useSession } from '../authentication/authenticationContext'; -import { useProductInventoryHostsConfig, useProductInventoryHostsQuery } from '../productView/productViewContext'; +import { + useProduct, + useProductInventoryHostsConfig, + useProductInventoryHostsQuery +} from '../productView/productViewContext'; import { helpers } from '../../common'; import Table from '../table/table'; import { Loader } from '../loader/loader'; import { MinHeight } from '../minHeight/minHeight'; -import InventoryGuests from '../inventoryGuests/inventoryGuests'; +import { InventoryGuests } from '../inventoryGuests/inventoryGuests'; import { inventoryCardHelpers } from './inventoryCardHelpers'; import Pagination from '../pagination/pagination'; import { ToolbarFieldDisplayName } from '../toolbar/toolbarFieldDisplayName'; @@ -18,6 +23,11 @@ import { RHSM_API_QUERY_SET_TYPES } from '../../services/rhsm/rhsmConstants'; import { translate } from '../i18n/i18n'; import { useGetInstancesInventory, useOnPageInstances, useOnColumnSortInstances } from './inventoryCardContext'; +/** + * ToDo: Update table component and review the deep comparison use on data + * The newer table wrapper should remove the need to use the deep comparison, + * temporarily using to allow the move from the deprecated inventory. + */ /** * Set up inventory cards. Expand filters with base settings. * @@ -29,6 +39,7 @@ import { useGetInstancesInventory, useOnPageInstances, useOnColumnSortInstances * @param {Function} props.useGetInventory * @param {Function} props.useOnPage * @param {Function} props.useOnColumnSort + * @param {Function} props.useProduct * @param {Function} props.useProductInventoryConfig * @param {Function} props.useProductInventoryQuery * @param {Function} props.useSession @@ -45,18 +56,70 @@ const InventoryCard = ({ useGetInventory: useAliasGetInventory, useOnPage: useAliasOnPage, useOnColumnSort: useAliasOnColumnSort, + useProduct: useAliasProduct, useProductInventoryConfig: useAliasProductInventoryConfig, useProductInventoryQuery: useAliasProductInventoryQuery, useSession: useAliasSession }) => { + const [updatedColumnsRows, setUpdatedColumnsRows] = useState({ columnHeaders: [], rows: [] }); const sessionData = useAliasSession(); const query = useAliasProductInventoryQuery(); const onPage = useAliasOnPage(); const onColumnSort = useAliasOnColumnSort(); + const { productId } = useAliasProduct(); const { filters: filterInventoryData, settings } = useAliasProductInventoryConfig(); const { error, fulfilled, pending, data = {} } = useAliasGetInventory({ isDisabled }); const { data: listData = [], meta = {} } = data; + useDeepCompareEffect(() => { + if (fulfilled && listData.length) { + let updatedColumnHeaders = []; + const updatedRows = listData.map(({ ...cellData }) => { + const { columnHeaders, cells } = inventoryCardHelpers.parseRowCellsListData({ + filters: inventoryCardHelpers.parseInventoryFilters({ + filters: filterInventoryData, + onSort: onColumnSort, + query + }), + cellData, + meta, + session: sessionData, + productId + }); + + updatedColumnHeaders = columnHeaders; + const subscriptionManagerId = cellData?.subscriptionManagerId; + const numberOfGuests = cellData?.numberOfGuests; + let isSubTable; + + // Is there a subTable, callback, or attempt to determine, return boolean + if (typeof settings?.hasSubTable === 'function') { + isSubTable = settings.hasSubTable({ ...cellData }, { ...sessionData }); + } else { + isSubTable = numberOfGuests > 0 && subscriptionManagerId; + } + + return { + cells, + expandedContent: + (isSubTable && ( + + )) || + undefined + }; + }); + + setUpdatedColumnsRows(() => ({ + columnHeaders: updatedColumnHeaders, + rows: updatedRows + })); + } + }, [filterInventoryData, fulfilled, listData]); + if (isDisabled) { return ( @@ -79,56 +142,6 @@ const InventoryCard = ({ (error === true && `bodyMinHeight-${updatedPerPage}-resize`) || `bodyMinHeight-${updatedPerPage}`; - /** - * Render an inventory table. - * - * @returns {Node} - */ - const renderTable = () => { - let updatedColumnHeaders = []; - - const updatedRows = listData.map(({ ...cellData }) => { - const { columnHeaders, cells } = inventoryCardHelpers.parseRowCellsListData({ - filters: inventoryCardHelpers.parseInventoryFilters({ - filters: filterInventoryData, - onSort: onColumnSort, - query - }), - cellData, - meta, - session: sessionData - }); - - updatedColumnHeaders = columnHeaders; - const subscriptionManagerId = cellData?.subscriptionManagerId; - const numberOfGuests = cellData?.numberOfGuests; - let isSubTable; - - // Is there a subTable, callback, or attempt to determine, return boolean - if (typeof settings?.hasSubTable === 'function') { - isSubTable = settings.hasSubTable({ ...cellData }, { ...sessionData }); - } else { - isSubTable = numberOfGuests > 0 && subscriptionManagerId; - } - - return { - cells, - expandedContent: - (isSubTable && ) || undefined - }; - }); - - return ( -
- ); - }; - return ( @@ -163,7 +176,15 @@ const InventoryCard = ({ }} /> )} - {!pending && renderTable()} + {!pending && ( +
+ )} @@ -192,9 +213,9 @@ const InventoryCard = ({ /** * Prop types. * - * @type {{cardActions: React.ReactNode, useSession: Function, useOnPage: Function, t: Function, perPageDefault: number, - * isDisabled: boolean, useProductInventoryConfig: Function, useGetInventory: Function, useOnColumnSort: Function, - * useProductInventoryQuery: Function}} + * @type {{cardActions: React.ReactNode, useSession: Function, useOnPage: Function, useProduct: Function, t: Function, + * perPageDefault: number, isDisabled: boolean, useProductInventoryConfig: Function, useGetInventory: Function, + * useOnColumnSort: Function, useProductInventoryQuery: Function}} */ InventoryCard.propTypes = { cardActions: PropTypes.node, @@ -204,6 +225,7 @@ InventoryCard.propTypes = { useGetInventory: PropTypes.func, useOnPage: PropTypes.func, useOnColumnSort: PropTypes.func, + useProduct: PropTypes.func, useProductInventoryConfig: PropTypes.func, useProductInventoryQuery: PropTypes.func, useSession: PropTypes.func @@ -212,9 +234,9 @@ InventoryCard.propTypes = { /** * Default props. * - * @type {{cardActions: React.ReactNode, useSession: Function, useOnPage: Function, t: Function, perPageDefault: number, - * isDisabled: boolean, useProductInventoryConfig: Function, useGetInventory: Function, useOnColumnSort: Function, - * useProductInventoryQuery: Function}} + * @type {{cardActions: React.ReactNode, useSession: Function, useOnPage: Function, useProduct: Function, t: translate, + * perPageDefault: number, isDisabled: boolean, useProductInventoryConfig: Function, useGetInventory: Function, + * useOnColumnSort: Function, useProductInventoryQuery: Function}} */ InventoryCard.defaultProps = { cardActions: ( @@ -228,6 +250,7 @@ InventoryCard.defaultProps = { useGetInventory: useGetInstancesInventory, useOnPage: useOnPageInstances, useOnColumnSort: useOnColumnSortInstances, + useProduct, useProductInventoryConfig: useProductInventoryHostsConfig, useProductInventoryQuery: useProductInventoryHostsQuery, useSession diff --git a/src/components/inventoryCard/inventoryCardHelpers.js b/src/components/inventoryCard/inventoryCardHelpers.js index 9c39ae827..ca44ea98b 100644 --- a/src/components/inventoryCard/inventoryCardHelpers.js +++ b/src/components/inventoryCard/inventoryCardHelpers.js @@ -47,10 +47,11 @@ const applyConfigProperty = (prop, { params = [] } = {}) => { * sortActive: boolean, sortDirection: string, transforms: Array}>} params.filters * @param {object} params.cellData * @param {object} params.meta + * @param {string} params.productId * @param {object} params.session * @returns {{bodyCells: { title: React.ReactNode }[], headerCells: { title: React.ReactNode }[]}} */ -const applyHeaderRowCellFilters = ({ filters = [], cellData = {}, meta = {}, session = {} } = {}) => { +const applyHeaderRowCellFilters = ({ filters = [], cellData = {}, meta = {}, productId, session = {} } = {}) => { const headerCells = []; const bodyCells = []; @@ -68,7 +69,10 @@ const applyHeaderRowCellFilters = ({ filters = [], cellData = {}, meta = {}, ses sortDirection, transforms }) => { - const headerCellUpdated = { title: translate('curiosity-inventory.header', { context: id }), transforms: [] }; + const headerCellUpdated = { + title: translate('curiosity-inventory.header', { context: [id, productId] }), + transforms: [] + }; const bodyCellUpdated = { title: '' }; // set filtered base header and body cells, or if filter doesn't exist skip @@ -279,10 +283,11 @@ const parseInventoryFilters = ({ filters = [], onSort, query = {} } = {}) => * transforms: Array}>} params.filters * @param {object} params.cellData * @param {object} params.meta + * @param {string} params.productId * @param {object} params.session * @returns {{columnHeaders: { title: React.ReactNode }[], cells: { title: React.ReactNode }[], data: {}}} */ -const parseRowCellsListData = ({ filters = [], cellData = {}, meta = {}, session = {} } = {}) => { +const parseRowCellsListData = ({ filters = [], cellData = {}, meta = {}, productId, session = {} } = {}) => { const updatedColumnHeaders = []; const updatedCells = []; const allCells = {}; @@ -290,7 +295,7 @@ const parseRowCellsListData = ({ filters = [], cellData = {}, meta = {}, session // Apply basic translation and value Object.entries(cellData).forEach(([key, value = '']) => { allCells[key] = { - title: translate('curiosity-inventory.header', { context: key }), + title: translate('curiosity-inventory.header', { context: [key, productId] }), value }; @@ -307,6 +312,7 @@ const parseRowCellsListData = ({ filters = [], cellData = {}, meta = {}, session filters, cellData: allCells, meta, + productId, session }); diff --git a/src/components/inventoryCardSubscriptions/__tests__/__snapshots__/inventoryCardSubscriptions.test.js.snap b/src/components/inventoryCardSubscriptions/__tests__/__snapshots__/inventoryCardSubscriptions.test.js.snap index a56181c25..f6fd1d8ba 100644 --- a/src/components/inventoryCardSubscriptions/__tests__/__snapshots__/inventoryCardSubscriptions.test.js.snap +++ b/src/components/inventoryCardSubscriptions/__tests__/__snapshots__/inventoryCardSubscriptions.test.js.snap @@ -9,6 +9,7 @@ exports[`InventoryCardSubscriptions Component should render a basic component: b useGetInventory={[Function]} useOnColumnSort={[Function]} useOnPage={[Function]} + useProduct={[Function]} useProductInventoryConfig={[Function]} useProductInventoryQuery={[Function]} useSession={[Function]} diff --git a/src/components/inventoryGuests/__tests__/__snapshots__/inventoryGuests.test.js.snap b/src/components/inventoryGuests/__tests__/__snapshots__/inventoryGuests.test.js.snap index 536937a06..d036d2dc3 100644 --- a/src/components/inventoryGuests/__tests__/__snapshots__/inventoryGuests.test.js.snap +++ b/src/components/inventoryGuests/__tests__/__snapshots__/inventoryGuests.test.js.snap @@ -16,7 +16,7 @@ exports[`GuestsList Component should handle an onScroll event: scroll event 1`] exports[`GuestsList Component should handle multiple display states: fulfilled 1`] = `
- + > +
+
+ +
+
+
`; exports[`GuestsList Component should handle variations in data: filtered data 1`] = `
- + > +
+
+ +
+
+
`; diff --git a/src/components/inventoryGuests/__tests__/__snapshots__/inventoryGuestsContext.test.js.snap b/src/components/inventoryGuests/__tests__/__snapshots__/inventoryGuestsContext.test.js.snap index 56547a5c0..898d54823 100644 --- a/src/components/inventoryGuests/__tests__/__snapshots__/inventoryGuestsContext.test.js.snap +++ b/src/components/inventoryGuests/__tests__/__snapshots__/inventoryGuestsContext.test.js.snap @@ -5,7 +5,7 @@ exports[`InventorySubscriptionsContext should handle an onScroll event: onPage e [ [ { - "offset": 1, + "offset": 100, "type": "SET_QUERY_RHSM_GUESTS_INVENTORY_offset", "viewId": "1234567890", }, @@ -22,6 +22,10 @@ exports[`InventorySubscriptionsContext should handle an onScroll event: onPage e "type": "SET_QUERY_CLEAR_INVENTORY_GUESTS_LIST", "viewId": "1234567890", }, + { + "id": "1234567890", + "type": "CLEAR_INVENTORY_GUESTS", + }, ], ], ] @@ -30,23 +34,30 @@ exports[`InventorySubscriptionsContext should handle an onScroll event: onPage e exports[`InventorySubscriptionsContext should handle instances inventory API responses: inventory, cancelled 1`] = ` { "cancelled": true, + "data": [], + "fulfilled": false, } `; exports[`InventorySubscriptionsContext should handle instances inventory API responses: inventory, error 1`] = ` { + "data": [], "error": true, + "fulfilled": false, } `; exports[`InventorySubscriptionsContext should handle instances inventory API responses: inventory, fulfilled 1`] = ` { + "data": [], "fulfilled": true, } `; exports[`InventorySubscriptionsContext should handle instances inventory API responses: inventory, pending 1`] = ` { + "data": [], + "fulfilled": false, "pending": true, } `; diff --git a/src/components/inventoryGuests/__tests__/inventoryGuests.test.js b/src/components/inventoryGuests/__tests__/inventoryGuests.test.js index 9e33a262b..53419b038 100644 --- a/src/components/inventoryGuests/__tests__/inventoryGuests.test.js +++ b/src/components/inventoryGuests/__tests__/inventoryGuests.test.js @@ -18,12 +18,10 @@ describe('GuestsList Component', () => { id: 'lorem', numberOfGuests: 2, useGetGuestsInventory: () => ({ - data: { - data: [ - { lorem: 'ipsum', dolor: 'sit' }, - { lorem: 'amet', dolor: 'amet' } - ] - } + data: [ + { lorem: 'ipsum', dolor: 'sit' }, + { lorem: 'amet', dolor: 'amet' } + ] }) }; @@ -53,10 +51,7 @@ describe('GuestsList Component', () => { component.setProps({ useGetGuestsInventory: () => ({ - fulfilled: true, - data: { - data: [{ lorem: 'ipsum', dolor: 'sit' }] - } + data: [{ lorem: 'ipsum', dolor: 'sit' }] }) }); @@ -71,9 +66,7 @@ describe('GuestsList Component', () => { useOnScroll: () => mockOnScroll, useGetGuestsInventory: () => ({ fulfilled: true, - data: { - data: [{ lorem: 'ipsum', dolor: 'sit' }] - } + data: [{ lorem: 'ipsum', dolor: 'sit' }] }) }; diff --git a/src/components/inventoryGuests/__tests__/inventoryGuestsContext.test.js b/src/components/inventoryGuests/__tests__/inventoryGuestsContext.test.js index f24471bab..aa22f0603 100644 --- a/src/components/inventoryGuests/__tests__/inventoryGuestsContext.test.js +++ b/src/components/inventoryGuests/__tests__/inventoryGuestsContext.test.js @@ -50,10 +50,9 @@ describe('InventorySubscriptionsContext', () => { it('should handle an onScroll event', async () => { const mockDispatch = jest.fn(); - const mockSuccessCallback = jest.fn(); const { unmount } = await mountHook(() => { - const onScroll = useOnScroll('1234567890', mockSuccessCallback, { + const onScroll = useOnScroll('1234567890', { useDispatch: () => mockDispatch, useProductInventoryQuery: () => ({ [RHSM_API_QUERY_SET_TYPES.OFFSET]: 0, @@ -68,9 +67,6 @@ describe('InventorySubscriptionsContext', () => { await unmount(); expect(mockDispatch.mock.calls).toMatchSnapshot('onPage event, dispatch'); - expect(mockSuccessCallback).toHaveBeenCalledTimes(1); - mockDispatch.mockClear(); - mockSuccessCallback.mockClear(); }); }); diff --git a/src/components/inventoryGuests/inventoryGuests.js b/src/components/inventoryGuests/inventoryGuests.js index 821f2ff56..b142b58cd 100644 --- a/src/components/inventoryGuests/inventoryGuests.js +++ b/src/components/inventoryGuests/inventoryGuests.js @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; import { TableVariant } from '@patternfly/react-table'; import { useSession } from '../authentication/authenticationContext'; @@ -34,36 +34,40 @@ const InventoryGuests = ({ useProductInventoryGuestsConfig: useAliasProductInventoryGuestsConfig, useSession: useAliasSession }) => { - const [previousData, setPreviousData] = useState([]); const sessionData = useAliasSession(); const { filters: filterGuestsData } = useAliasProductInventoryGuestsConfig(); - + const { pending, data: listData = [] } = useAliasGetGuestsInventory(id); + const onScroll = useAliasOnScroll(id); const query = useAliasProductInventoryGuestsQuery({ options: { overrideId: id } }); const { [RHSM_API_QUERY_SET_TYPES.OFFSET]: currentPage } = query; - const { error, pending, data = {} } = useAliasGetGuestsInventory(id); - const { data: listData = [] } = data; - - const onScroll = useAliasOnScroll(id, () => { - const updatedData = [...previousData, ...(listData || [])]; - setPreviousData(updatedData); - }); - /** * Render a scroll table loader. * + * @param {boolean} isFirstPage * @returns {Node} */ - const renderLoader = () => { - if (currentPage > 0 && pending) { + const renderLoader = isFirstPage => { + if (pending) { + let updatedRowCount = 0; + + if (isFirstPage) { + if (numberOfGuests < defaultPerPage) { + updatedRowCount = numberOfGuests; + } else { + updatedRowCount = defaultPerPage; + } + } + const scrollLoader = ( cellWidth)) || [], - rowCount: 0, + rowCount: updatedRowCount, variant: TableVariant.compact }} /> @@ -75,40 +79,34 @@ const InventoryGuests = ({ return null; }; - /** - * Render a guests table. - * - * @returns {Node} - */ - const renderTable = () => { - let updatedColumnHeaders = []; - - const updatedRows = [...previousData, ...(listData || [])].map(({ ...cellData }) => { - const { columnHeaders, cells } = inventoryCardHelpers.parseRowCellsListData({ - filters: filterGuestsData, - cellData, - session: sessionData - }); + let updatedColumnHeaders = []; + const updatedRows = listData.map(({ ...cellData }) => { + const { columnHeaders, cells } = inventoryCardHelpers.parseRowCellsListData({ + filters: filterGuestsData, + cellData, + session: sessionData + }); - updatedColumnHeaders = columnHeaders; + updatedColumnHeaders = columnHeaders; - return { - cells - }; - }); + return { + cells + }; + }); - // ToDo: Review having the height be a calc value - // Include the table header - let updatedHeight = (numberOfGuests + 1) * 42; - updatedHeight = (updatedHeight < 275 && updatedHeight) || 275; + // ToDo: Review having the height be a calc value + // Include the table header + let updatedHeight = (numberOfGuests + 1) * 42; + updatedHeight = (updatedHeight < 275 && updatedHeight) || 275; - return ( + return ( +
- {renderLoader()} + {renderLoader(currentPage === 0)} {(updatedRows.length && (
- ); - }; - - return ( -
- {pending && currentPage === 0 && ( - cellWidth)) || [], - rowCount: numberOfGuests < defaultPerPage ? numberOfGuests : defaultPerPage, - variant: TableVariant.compact - }} - /> - )} - {((!pending && currentPage === 0) || currentPage > 0) && renderTable()}
); }; diff --git a/src/components/inventoryGuests/inventoryGuestsContext.js b/src/components/inventoryGuests/inventoryGuestsContext.js index 583e1109d..0a74cf3d6 100644 --- a/src/components/inventoryGuests/inventoryGuestsContext.js +++ b/src/components/inventoryGuests/inventoryGuestsContext.js @@ -1,4 +1,5 @@ -import { useUnmount, useShallowCompareEffect } from 'react-use'; +import { useState } from 'react'; +import { useDeepCompareEffect, useUnmount, useShallowCompareEffect } from 'react-use'; import { reduxActions, reduxTypes, storeHooks } from '../../redux'; import { useProductInventoryGuestsQuery } from '../productView/productViewContext'; import { RHSM_API_QUERY_SET_TYPES } from '../../services/rhsm/rhsmConstants'; @@ -47,15 +48,25 @@ const useGetGuestsInventory = ( useSelectorsInventory: useAliasSelectorsInventory = useSelectorsGuestsInventory } = {} ) => { + const [updatedData, setUpdatedData] = useState([]); const query = useAliasProductInventoryQuery({ options: { overrideId: id } }); const dispatch = useAliasDispatch(); - const response = useAliasSelectorsInventory(id); + const { data = {}, fulfilled = false, ...response } = useAliasSelectorsInventory(id); + const { data: listData = [] } = data; useShallowCompareEffect(() => { getInventory(id, query)(dispatch); }, [dispatch, id, query]); + useDeepCompareEffect(() => { + if (fulfilled) { + setUpdatedData(prevState => [...prevState, ...listData]); + } + }, [fulfilled, listData]); + return { + data: updatedData, + fulfilled, ...response }; }; @@ -64,7 +75,6 @@ const useGetGuestsInventory = ( * Use paging as onScroll event for guests inventory. * * @param {string} id - * @param {Function} successCallback * @param {object} options * @param {Function} options.useDispatch * @param {Function} options.useSelectorsInventory @@ -73,7 +83,6 @@ const useGetGuestsInventory = ( */ const useOnScroll = ( id, - successCallback, { useDispatch: useAliasDispatch = storeHooks.reactRedux.useDispatch, useSelectorsInventory: useAliasSelectorsInventory = useSelectorsGuestsInventory, @@ -95,6 +104,10 @@ const useOnScroll = ( { type: reduxTypes.query.SET_QUERY_CLEAR_INVENTORY_GUESTS_LIST, viewId: id + }, + { + type: reduxTypes.inventory.CLEAR_INVENTORY_GUESTS, + id } ]); }); @@ -110,16 +123,12 @@ const useOnScroll = ( const { target } = event; const bottom = target.scrollHeight - target.scrollTop === target.clientHeight; - if (numberOfGuests > (currentPage + 1) * limit && bottom && !pending) { - if (typeof successCallback === 'function') { - successCallback(event); - } - + if (numberOfGuests > currentPage + limit && bottom && !pending) { dispatch([ { type: reduxTypes.query.SET_QUERY_RHSM_GUESTS_INVENTORY_TYPES[RHSM_API_QUERY_SET_TYPES.OFFSET], viewId: id, - [RHSM_API_QUERY_SET_TYPES.OFFSET]: currentPage + 1 + [RHSM_API_QUERY_SET_TYPES.OFFSET]: currentPage + limit }, { type: reduxTypes.query.SET_QUERY_RHSM_GUESTS_INVENTORY_TYPES[RHSM_API_QUERY_SET_TYPES.LIMIT], diff --git a/src/components/productView/__tests__/__snapshots__/productView.test.js.snap b/src/components/productView/__tests__/__snapshots__/productView.test.js.snap index 9427408c2..ae9397547 100644 --- a/src/components/productView/__tests__/__snapshots__/productView.test.js.snap +++ b/src/components/productView/__tests__/__snapshots__/productView.test.js.snap @@ -359,6 +359,7 @@ exports[`ProductView Component should use an instances inventory for rhosak: cus useGetInventory={[Function]} useOnColumnSort={[Function]} useOnPage={[Function]} + useProduct={[Function]} useProductInventoryConfig={[Function]} useProductInventoryQuery={[Function]} useSession={[Function]} diff --git a/src/redux/reducers/__tests__/__snapshots__/inventoryReducer.test.js.snap b/src/redux/reducers/__tests__/__snapshots__/inventoryReducer.test.js.snap index aad40733e..eb04ba283 100644 --- a/src/redux/reducers/__tests__/__snapshots__/inventoryReducer.test.js.snap +++ b/src/redux/reducers/__tests__/__snapshots__/inventoryReducer.test.js.snap @@ -252,6 +252,21 @@ exports[`InventoryReducer should handle all defined pending types: pending types } `; +exports[`InventoryReducer should handle specific defined types: defined type CLEAR_INVENTORY_GUESTS 1`] = ` +{ + "result": { + "hostsGuests": { + "lorem": {}, + }, + "hostsInventory": {}, + "instancesInventory": {}, + "subscriptionsInventory": {}, + "tabs": {}, + }, + "type": "CLEAR_INVENTORY_GUESTS", +} +`; + exports[`InventoryReducer should handle specific defined types: defined type SET_INVENTORY_TAB 1`] = ` { "result": { diff --git a/src/redux/reducers/__tests__/inventoryReducer.test.js b/src/redux/reducers/__tests__/inventoryReducer.test.js index 3512eee29..043382112 100644 --- a/src/redux/reducers/__tests__/inventoryReducer.test.js +++ b/src/redux/reducers/__tests__/inventoryReducer.test.js @@ -8,14 +8,15 @@ describe('InventoryReducer', () => { }); it('should handle specific defined types', () => { - const specificTypes = [inventoryTypes.SET_INVENTORY_TAB]; + const specificTypes = [inventoryTypes.SET_INVENTORY_TAB, inventoryTypes.CLEAR_INVENTORY_GUESTS]; specificTypes.forEach(value => { const dispatched = { type: value, tabs: { lorem: 1 - } + }, + id: 'lorem' }; const resultState = inventoryReducer(undefined, dispatched); diff --git a/src/redux/reducers/inventoryReducer.js b/src/redux/reducers/inventoryReducer.js index d993a2929..f2339d14d 100644 --- a/src/redux/reducers/inventoryReducer.js +++ b/src/redux/reducers/inventoryReducer.js @@ -37,6 +37,17 @@ const inventoryReducer = (state = initialState, action) => { reset: false } ); + case inventoryTypes.CLEAR_INVENTORY_GUESTS: + return reduxHelpers.setStateProp( + 'hostsGuests', + { + [action.id]: {} + }, + { + state, + reset: false + } + ); default: return reduxHelpers.generatedPromiseActionReducer( [ diff --git a/src/redux/types/__tests__/__snapshots__/index.test.js.snap b/src/redux/types/__tests__/__snapshots__/index.test.js.snap index 138e9d215..564b70673 100644 --- a/src/redux/types/__tests__/__snapshots__/index.test.js.snap +++ b/src/redux/types/__tests__/__snapshots__/index.test.js.snap @@ -15,6 +15,7 @@ exports[`ReduxTypes should have specific type properties: all redux types 1`] = "SET_GRAPH_LEGEND": "SET_GRAPH_LEGEND", }, "inventory": { + "CLEAR_INVENTORY_GUESTS": "CLEAR_INVENTORY_GUESTS", "SET_INVENTORY_TAB": "SET_INVENTORY_TAB", }, "platform": { @@ -86,6 +87,7 @@ exports[`ReduxTypes should have specific type properties: all redux types 1`] = "SET_GRAPH_LEGEND": "SET_GRAPH_LEGEND", }, "inventoryTypes": { + "CLEAR_INVENTORY_GUESTS": "CLEAR_INVENTORY_GUESTS", "SET_INVENTORY_TAB": "SET_INVENTORY_TAB", }, "platformTypes": { @@ -140,6 +142,7 @@ exports[`ReduxTypes should have specific type properties: all redux types 1`] = "SET_GRAPH_LEGEND": "SET_GRAPH_LEGEND", }, "inventory": { + "CLEAR_INVENTORY_GUESTS": "CLEAR_INVENTORY_GUESTS", "SET_INVENTORY_TAB": "SET_INVENTORY_TAB", }, "platform": { @@ -241,6 +244,7 @@ exports[`ReduxTypes should have specific type properties: specific types 1`] = ` "SET_GRAPH_LEGEND": "SET_GRAPH_LEGEND", }, "inventory": { + "CLEAR_INVENTORY_GUESTS": "CLEAR_INVENTORY_GUESTS", "SET_INVENTORY_TAB": "SET_INVENTORY_TAB", }, "platform": { diff --git a/src/redux/types/inventoryTypes.js b/src/redux/types/inventoryTypes.js index 0261714be..3bf9b7d92 100644 --- a/src/redux/types/inventoryTypes.js +++ b/src/redux/types/inventoryTypes.js @@ -1,12 +1,14 @@ +const CLEAR_INVENTORY_GUESTS = 'CLEAR_INVENTORY_GUESTS'; const SET_INVENTORY_TAB = 'SET_INVENTORY_TAB'; /** * Inventory action, reducer types. * - * @type {{SET_INVENTORY_TAB: string}} + * @type {{CLEAR_INVENTORY_GUESTS: string, SET_INVENTORY_TAB: string}} */ const inventoryTypes = { + CLEAR_INVENTORY_GUESTS, SET_INVENTORY_TAB }; -export { inventoryTypes as default, inventoryTypes, SET_INVENTORY_TAB }; +export { inventoryTypes as default, inventoryTypes, CLEAR_INVENTORY_GUESTS, SET_INVENTORY_TAB }; diff --git a/tests/__snapshots__/code.test.js.snap b/tests/__snapshots__/code.test.js.snap index 08c84b8e6..ece4cabae 100644 --- a/tests/__snapshots__/code.test.js.snap +++ b/tests/__snapshots__/code.test.js.snap @@ -3,7 +3,7 @@ exports[`General code checks should only have specific console.[warn|log|info|error] methods: console methods 1`] = ` [ "components/inventoryCard/inventoryCardContext.js:168: console.warn(\`Sorting can only be performed on select fields, confirm field \${id} is allowed.\`);", - "components/inventoryCard/inventoryCardHelpers.js:83: console.warn(\`Warning: Filter "\${id}" not found in "table row" response data.\`, cellData);", + "components/inventoryCard/inventoryCardHelpers.js:87: console.warn(\`Warning: Filter "\${id}" not found in "table row" response data.\`, cellData);", "components/inventoryCard/inventoryList.deprecated.js:62: console.warn(\`Sorting can only be performed on select fields, confirm field \${id} is allowed.\`);", "components/inventoryCardSubscriptions/inventoryCardSubscriptionsContext.js:127: console.warn(\`Sorting can only be performed on select fields, confirm field \${id} is allowed.\`);", "index.js:12: console.log(\`Emulated appNavClick: \${JSON.stringify({ id, ...rest })}\`);", diff --git a/tests/__snapshots__/dist.test.js.snap b/tests/__snapshots__/dist.test.js.snap index 4f15d6215..de9323ddc 100644 --- a/tests/__snapshots__/dist.test.js.snap +++ b/tests/__snapshots__/dist.test.js.snap @@ -15,6 +15,7 @@ exports[`Build distribution should match a specific file output 1`] = ` "./dist/fed-mods.json", "./dist/fonts/graph2x.png", "./dist/fonts/graph4x.png", + "./dist/js/1009*js", "./dist/js/1132*js", "./dist/js/1233*js", "./dist/js/1302*js", @@ -31,7 +32,6 @@ exports[`Build distribution should match a specific file output 1`] = ` "./dist/js/2211*txt", "./dist/js/2217*js", "./dist/js/2243*js", - "./dist/js/2252*js", "./dist/js/2293*js", "./dist/js/2738*js", "./dist/js/2881*js", @@ -44,11 +44,12 @@ exports[`Build distribution should match a specific file output 1`] = ` "./dist/js/3557*js", "./dist/js/3577*js", "./dist/js/3722*js", + "./dist/js/3768*js", "./dist/js/384*js", "./dist/js/3914*js", "./dist/js/3935*js", "./dist/js/3935*txt", - "./dist/js/4023*js", + "./dist/js/4021*js", "./dist/js/4024*js", "./dist/js/4044*js", "./dist/js/4097*js", @@ -64,13 +65,12 @@ exports[`Build distribution should match a specific file output 1`] = ` "./dist/js/5242*js", "./dist/js/5250*js", "./dist/js/5250*txt", - "./dist/js/5255*js", "./dist/js/5394*js", - "./dist/js/5451*js", "./dist/js/5473*js", "./dist/js/5876*js", "./dist/js/5993*js", "./dist/js/6402*js", + "./dist/js/6476*js", "./dist/js/6706*js", "./dist/js/6706*txt", "./dist/js/6816*js", @@ -79,6 +79,7 @@ exports[`Build distribution should match a specific file output 1`] = ` "./dist/js/7235*js", "./dist/js/7294*js", "./dist/js/7294*txt", + "./dist/js/7297*js", "./dist/js/7585*js", "./dist/js/7745*js", "./dist/js/7780*js", @@ -91,7 +92,6 @@ exports[`Build distribution should match a specific file output 1`] = ` "./dist/js/8900*js", "./dist/js/9051*js", "./dist/js/9077*js", - "./dist/js/9110*js", "./dist/js/9222*js", "./dist/js/9270*js", "./dist/js/9283*js", @@ -107,6 +107,7 @@ exports[`Build distribution should match a specific file output 1`] = ` "./dist/locales/en-US.json", "./dist/locales/en.json", "./dist/locales/locales.json", + "./dist/sourcemaps/1009*map", "./dist/sourcemaps/1233*map", "./dist/sourcemaps/1302*map", "./dist/sourcemaps/1339*map", @@ -121,7 +122,6 @@ exports[`Build distribution should match a specific file output 1`] = ` "./dist/sourcemaps/2211*map", "./dist/sourcemaps/2217*map", "./dist/sourcemaps/2243*map", - "./dist/sourcemaps/2252*map", "./dist/sourcemaps/2293*map", "./dist/sourcemaps/2738*map", "./dist/sourcemaps/2881*map", @@ -134,10 +134,11 @@ exports[`Build distribution should match a specific file output 1`] = ` "./dist/sourcemaps/3557*map", "./dist/sourcemaps/3577*map", "./dist/sourcemaps/3722*map", + "./dist/sourcemaps/3768*map", "./dist/sourcemaps/384*map", "./dist/sourcemaps/3914*map", "./dist/sourcemaps/3935*map", - "./dist/sourcemaps/4023*map", + "./dist/sourcemaps/4021*map", "./dist/sourcemaps/4024*map", "./dist/sourcemaps/4044*map", "./dist/sourcemaps/4097*map", @@ -150,19 +151,19 @@ exports[`Build distribution should match a specific file output 1`] = ` "./dist/sourcemaps/5068*map", "./dist/sourcemaps/5242*map", "./dist/sourcemaps/5250*map", - "./dist/sourcemaps/5255*map", "./dist/sourcemaps/5394*map", - "./dist/sourcemaps/5451*map", "./dist/sourcemaps/5473*map", "./dist/sourcemaps/5876*map", "./dist/sourcemaps/5993*map", "./dist/sourcemaps/6402*map", + "./dist/sourcemaps/6476*map", "./dist/sourcemaps/6706*map", "./dist/sourcemaps/6816*map", "./dist/sourcemaps/7080*map", "./dist/sourcemaps/7183*map", "./dist/sourcemaps/7235*map", "./dist/sourcemaps/7294*map", + "./dist/sourcemaps/7297*map", "./dist/sourcemaps/7585*map", "./dist/sourcemaps/7780*map", "./dist/sourcemaps/7970*map", @@ -173,7 +174,6 @@ exports[`Build distribution should match a specific file output 1`] = ` "./dist/sourcemaps/8900*map", "./dist/sourcemaps/9051*map", "./dist/sourcemaps/9077*map", - "./dist/sourcemaps/9110*map", "./dist/sourcemaps/9222*map", "./dist/sourcemaps/9270*map", "./dist/sourcemaps/9283*map",