diff --git a/src/components/bannerMessages/__tests__/__snapshots__/bannerMessages.test.js.snap b/src/components/bannerMessages/__tests__/__snapshots__/bannerMessages.test.js.snap index fa995f2da..bb4a52d27 100644 --- a/src/components/bannerMessages/__tests__/__snapshots__/bannerMessages.test.js.snap +++ b/src/components/bannerMessages/__tests__/__snapshots__/bannerMessages.test.js.snap @@ -13,8 +13,7 @@ exports[`BannerMessages Component should handle closing messages from state: sta }, ] } - useAppMessages={[Function]} - useRouteDetail={[Function]} + useGetAppMessages={[Function]} >
`; -exports[`BannerMessages Component should render a non-connected component: non-connected 1`] = ` +exports[`BannerMessages Component should render a basic component: basic 1`] = `
`; - -exports[`BannerMessages Component should render specific messages when the appMessages prop is used: specific messages, OFF, ON 1`] = ` - -
- - } - key="dolorSit" - title="Dolor sit title" - variant="info" - > -
- -
- - - - - -
-
-

- - Info alert: - - Dolor sit title -

-
- - - - - -
-
- Dolor sit message -
-
-
-
-
-`; diff --git a/src/components/bannerMessages/__tests__/__snapshots__/bannerMessagesContext.test.js.snap b/src/components/bannerMessages/__tests__/__snapshots__/bannerMessagesContext.test.js.snap new file mode 100644 index 000000000..0a12654f4 --- /dev/null +++ b/src/components/bannerMessages/__tests__/__snapshots__/bannerMessagesContext.test.js.snap @@ -0,0 +1,51 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`BannerMessagesContext should apply a hook for retrieving messages data from a selectors: error response 1`] = ` +Object { + "data": Object { + "cloudigradeMismatch": false, + }, + "error": true, + "fulfilled": undefined, + "pending": undefined, +} +`; + +exports[`BannerMessagesContext should apply a hook for retrieving messages data from a selectors: mock store error response 1`] = ` +Object { + "data": Object { + "cloudigradeMismatch": false, + }, + "error": true, + "fulfilled": false, + "pending": false, +} +`; + +exports[`BannerMessagesContext should apply a hook for retrieving messages data from a selectors: mock store success response 1`] = ` +Object { + "data": Object { + "cloudigradeMismatch": true, + }, + "error": false, + "fulfilled": true, + "pending": false, +} +`; + +exports[`BannerMessagesContext should apply a hook for retrieving messages data from a selectors: success response 1`] = ` +Object { + "data": Object { + "cloudigradeMismatch": false, + }, + "error": undefined, + "fulfilled": true, + "pending": undefined, +} +`; + +exports[`BannerMessagesContext should return specific properties: specific properties 1`] = ` +Object { + "useGetAppMessages": [Function], +} +`; diff --git a/src/components/bannerMessages/__tests__/bannerMessages.test.js b/src/components/bannerMessages/__tests__/bannerMessages.test.js index 8bd2bc442..844f2ac5e 100644 --- a/src/components/bannerMessages/__tests__/bannerMessages.test.js +++ b/src/components/bannerMessages/__tests__/bannerMessages.test.js @@ -3,7 +3,7 @@ import { AlertActionCloseButton } from '@patternfly/react-core'; import { BannerMessages } from '../bannerMessages'; describe('BannerMessages Component', () => { - it('should render a non-connected component', async () => { + it('should render a basic component', async () => { const props = { messages: [ { @@ -12,52 +12,15 @@ describe('BannerMessages Component', () => { message: 'Lorem ipsum message' } ], - useAppMessages: () => ({ appMessages: { loremIpsum: true } }), - useRouteDetail: () => ({}) - }; - - const component = await mountHookComponent(); - expect(component).toMatchSnapshot('non-connected'); - }); - - it('should attempt to check reports on product update', async () => { - const props = { - messages: [ - { - id: 'loremIpsum', - title: 'Lorem ipsum title', - message: 'Lorem ipsum message' - } - ], - useAppMessages: jest.fn(() => ({})), - useRouteDetail: () => ({ productConfig: [{ productId: 'lorem' }] }) - }; - - const component = await shallowHookComponent(); - component.setProps({ useRouteDetail: () => ({ productConfig: [{ productId: 'dolor' }] }) }); - expect(props.useAppMessages).toHaveBeenCalledTimes(2); - }); - - it('should render specific messages when the appMessages prop is used', async () => { - const props = { - messages: [ - { - id: 'loremIpsum', - title: 'Lorem ipsum title', - message: 'Lorem ipsum message' - }, - { - id: 'dolorSit', - title: 'Dolor sit title', - message: 'Dolor sit message' + useGetAppMessages: () => ({ + data: { + loremIpsum: true } - ], - useAppMessages: () => ({ appMessages: { loremIpsum: false, dolorSit: true } }), - useRouteDetail: () => ({}) + }) }; - const component = await mountHookComponent(); - expect(component).toMatchSnapshot('specific messages, OFF, ON'); + + expect(component).toMatchSnapshot('basic'); }); it('should handle closing messages from state', async () => { @@ -69,8 +32,7 @@ describe('BannerMessages Component', () => { message: 'Dolor sit message' } ], - useAppMessages: () => ({ appMessages: { dolorSit: true } }), - useRouteDetail: () => ({}) + useGetAppMessages: () => ({ data: { dolorSit: true } }) }; const component = await mountHookComponent(); diff --git a/src/components/bannerMessages/__tests__/bannerMessagesContext.test.js b/src/components/bannerMessages/__tests__/bannerMessagesContext.test.js new file mode 100644 index 000000000..563ccb19b --- /dev/null +++ b/src/components/bannerMessages/__tests__/bannerMessagesContext.test.js @@ -0,0 +1,86 @@ +import { context, useGetAppMessages } from '../bannerMessagesContext'; +import { rhsmConstants } from '../../../services/rhsm/rhsmConstants'; + +describe('BannerMessagesContext', () => { + it('should return specific properties', () => { + expect(context).toMatchSnapshot('specific properties'); + }); + + it('should apply a hook for retrieving messages data from a selectors', () => { + const { result: errorResponse } = shallowHook(() => + useGetAppMessages({ + useSelectorsResponse: () => ({ + error: true, + data: { + messages: {} + } + }) + }) + ); + + expect(errorResponse).toMatchSnapshot('error response'); + + const { result: successResponse } = shallowHook(() => + useGetAppMessages({ + useSelectorsResponse: () => ({ + fulfilled: true, + data: { + messages: {} + } + }) + }) + ); + + expect(successResponse).toMatchSnapshot('success response'); + + const { result: mockStoreSuccessResponse } = shallowHook( + () => + useGetAppMessages({ + useProduct: () => ({ productId: 'loremIpsum' }) + }), + { + state: { + messages: { + report: { + loremIpsum: { + fulfilled: true, + data: { + data: [ + { + [rhsmConstants.RHSM_API_RESPONSE_TALLY_META_TYPES.HAS_CLOUDIGRADE_MISMATCH]: true + } + ] + } + } + } + } + } + } + ); + + expect(mockStoreSuccessResponse).toMatchSnapshot('mock store success response'); + + const { result: mockStoreErrorResponse } = shallowHook( + () => + useGetAppMessages({ + useProduct: () => ({ productId: 'loremIpsum' }) + }), + { + state: { + messages: { + report: { + loremIpsum: { + error: true, + data: { + data: [] + } + } + } + } + } + } + ); + + expect(mockStoreErrorResponse).toMatchSnapshot('mock store error response'); + }); +}); diff --git a/src/components/bannerMessages/bannerMessages.js b/src/components/bannerMessages/bannerMessages.js index fa28b59d7..59fa9045e 100644 --- a/src/components/bannerMessages/bannerMessages.js +++ b/src/components/bannerMessages/bannerMessages.js @@ -3,42 +3,22 @@ import PropTypes from 'prop-types'; import { Alert, AlertActionCloseButton, AlertVariant, Button } from '@patternfly/react-core'; import { ExternalLinkAltIcon } from '@patternfly/react-icons'; import { useShallowCompareEffect } from 'react-use'; -import { apiQueries, storeHooks } from '../../redux'; +import { useGetAppMessages } from './bannerMessagesContext'; +import { helpers } from '../../common'; import { translate } from '../i18n/i18n'; -import { dateHelpers, helpers } from '../../common'; -import { RHSM_API_QUERY_GRANULARITY_TYPES as GRANULARITY_TYPES, RHSM_API_QUERY_TYPES } from '../../types/rhsmApiTypes'; -import { useRouteDetail } from '../../hooks/useRouter'; /** * Render banner messages. * * @param {object} props * @param {Array} props.messages - * @param {Function} props.useRouteDetail - * @param {Function} props.useAppMessages - * @returns {Node} + * @param {Function} props.useGetAppMessages + * @returns {React.ReactNode} */ -const BannerMessages = ({ messages, useRouteDetail: useAliasRouteDetail, useAppMessages: useAliasAppMessages }) => { +const BannerMessages = ({ messages, useGetAppMessages: useAliasGetAppMessages }) => { const [hideAlerts, setHideAlerts] = useState({}); const [alerts, setAlerts] = useState([]); - const { pathParameter: productId, productConfig } = useAliasRouteDetail() || {}; - const isProductConfig = productConfig?.length === 1 && productConfig?.[0]; - const { query } = apiQueries.parseRhsmQuery(productConfig?.[0]?.query || {}); - const { appMessages } = useAliasAppMessages(); - - useShallowCompareEffect(() => { - if (productId && isProductConfig) { - const { startDate, endDate } = dateHelpers.getRangedDateTime('CURRENT'); - const updatedGraphQuery = { - ...query, - [RHSM_API_QUERY_TYPES.GRANULARITY]: GRANULARITY_TYPES.DAILY, - [RHSM_API_QUERY_TYPES.START_DATE]: startDate.toISOString(), - [RHSM_API_QUERY_TYPES.END_DATE]: endDate.toISOString() - }; - - storeHooks.rhsmActions.useGetMessageReports(productId, updatedGraphQuery); - } - }, [productId, isProductConfig, query]); + const { data: appMessages } = useAliasGetAppMessages(); useShallowCompareEffect(() => { const updatedMessages = []; @@ -81,7 +61,7 @@ const BannerMessages = ({ messages, useRouteDetail: useAliasRouteDetail, useAppM /** * Prop types. * - * @type {{useAppMessages: Function, messages: Array, useRouteDetail: Function}} + * @type {{useGetAppMessages: Function, messages: Array}} */ BannerMessages.propTypes = { messages: PropTypes.arrayOf( @@ -92,14 +72,13 @@ BannerMessages.propTypes = { variant: PropTypes.oneOf([...Object.values(AlertVariant)]) }) ), - useAppMessages: PropTypes.func, - useRouteDetail: PropTypes.func + useGetAppMessages: PropTypes.func }; /** * Default props. * - * @type {{useAppMessages: Function, messages: Array, useRouteDetail: Function}} + * @type {{useGetAppMessages: Function, messages: Array}} */ BannerMessages.defaultProps = { messages: [ @@ -126,8 +105,7 @@ BannerMessages.defaultProps = { ) } ], - useAppMessages: storeHooks.rhsmSelectors.useAppMessages, - useRouteDetail + useGetAppMessages }; export { BannerMessages as default, BannerMessages }; diff --git a/src/components/bannerMessages/bannerMessagesContext.js b/src/components/bannerMessages/bannerMessagesContext.js new file mode 100644 index 000000000..15b709860 --- /dev/null +++ b/src/components/bannerMessages/bannerMessagesContext.js @@ -0,0 +1,86 @@ +import { useShallowCompareEffect } from 'react-use'; +import { reduxActions, storeHooks } from '../../redux'; +import { useProduct, useProductQuery } from '../productView/productViewContext'; +import { dateHelpers } from '../../common'; +import { + rhsmConstants, + RHSM_API_QUERY_GRANULARITY_TYPES as GRANULARITY_TYPES, + RHSM_API_QUERY_SET_TYPES +} from '../../services/rhsm/rhsmConstants'; + +/** + * ToDo: useGetAppMessages is setup to work with existing Tally response, pre-metrics + * Banner messages scans the returned Tally listing for the HAS_CLOUDIGRADE_MISMATCH. In the future + * this may need to be updated to pull from the "meta" object part of the Tally response. + */ +/** + * Get app messages. + * + * @param {object} options + * @param {Function} options.getMessageReports + * @param {Function} options.useDispatch + * @param {Function} options.useProduct + * @param {Function} options.useProductQuery + * @param {Function} options.useSelectorsResponse + * @returns {object} + */ +const useGetAppMessages = ({ + getMessageReports = reduxActions.rhsm.getMessageReports, + useDispatch: useAliasDispatch = storeHooks.reactRedux.useDispatch, + useProduct: useAliasProduct = useProduct, + useProductQuery: useAliasProductQuery = useProductQuery, + useSelectorsResponse: useAliasSelectorsResponse = storeHooks.reactRedux.useSelectorsResponse +} = {}) => { + const { productId } = useAliasProduct(); + const query = useAliasProductQuery(); + const dispatch = useAliasDispatch(); + const { error, fulfilled, pending, data } = useAliasSelectorsResponse({ + id: 'messages', + selector: ({ messages }) => messages?.report?.[productId] + }); + + useShallowCompareEffect(() => { + if (productId) { + const { startDate, endDate } = dateHelpers.getRangedDateTime('CURRENT'); + const updatedQuery = { + ...query, + [RHSM_API_QUERY_SET_TYPES.GRANULARITY]: GRANULARITY_TYPES.DAILY, + [RHSM_API_QUERY_SET_TYPES.START_DATE]: startDate.toISOString(), + [RHSM_API_QUERY_SET_TYPES.END_DATE]: endDate.toISOString() + }; + + getMessageReports(productId, updatedQuery)(dispatch); + } + }, [productId, query]); + + const updatedData = { + cloudigradeMismatch: false + }; + + if (fulfilled) { + const { messages = {} } = data || {}; + + updatedData.cloudigradeMismatch = + messages?.data + ?.reverse() + ?.find( + ({ [rhsmConstants.RHSM_API_RESPONSE_TALLY_META_TYPES.HAS_CLOUDIGRADE_MISMATCH]: mismatch }) => + mismatch === true + ) !== undefined; + } + + return { + error, + fulfilled, + pending, + data: { + ...updatedData + } + }; +}; + +const context = { + useGetAppMessages +}; + +export { context as default, context, useGetAppMessages }; diff --git a/src/components/productView/__tests__/__snapshots__/productView.test.js.snap b/src/components/productView/__tests__/__snapshots__/productView.test.js.snap index 9af6a6229..1d5104859 100644 --- a/src/components/productView/__tests__/__snapshots__/productView.test.js.snap +++ b/src/components/productView/__tests__/__snapshots__/productView.test.js.snap @@ -10,23 +10,6 @@ exports[`ProductView Component should allow custom inventory displays via config > t(curiosity-view.title, {"appName":"Subscriptions","context":"lorem ipsum product label"}) - - - @@ -41,6 +24,22 @@ exports[`ProductView Component should allow custom inventory displays via config } } > + + + @@ -162,23 +161,6 @@ exports[`ProductView Component should allow custom product views via props: cust > t(curiosity-view.title, {"appName":"Subscriptions","context":"lorem ipsum product label"}) - - - @@ -192,6 +174,22 @@ exports[`ProductView Component should allow custom product views via props: cust } } > + + + @@ -298,23 +296,6 @@ exports[`ProductView Component should allow custom product views via props: cust > t(curiosity-view.title, {"appName":"Subscriptions","context":"lorem ipsum product label"}) - - - @@ -328,6 +309,22 @@ exports[`ProductView Component should allow custom product views via props: cust } } > + + + @@ -382,23 +379,6 @@ exports[`ProductView Component should render a basic component: basic 1`] = ` > t(curiosity-view.title, {"appName":"Subscriptions","context":"lorem ipsum product label"}) - - - @@ -412,6 +392,22 @@ exports[`ProductView Component should render a basic component: basic 1`] = ` } } > + + + @@ -496,23 +492,6 @@ exports[`ProductView Component should render nothing if path and product paramet > t(curiosity-view.title, {"appName":"Subscriptions"}) - - - diff --git a/src/components/productView/productView.js b/src/components/productView/productView.js index 04d796022..2cdf7b956 100644 --- a/src/components/productView/productView.js +++ b/src/components/productView/productView.js @@ -42,7 +42,7 @@ import { translate } from '../i18n/i18n'; * @returns {Node} */ const ProductView = ({ t, toolbarGraph, toolbarGraphDescription, useRouteDetail: useAliasRouteDetail }) => { - const { pathParameter: routeProductId, productParameter: routeProductLabel, productConfig } = useAliasRouteDetail(); + const { productParameter: routeProductLabel, productConfig } = useAliasRouteDetail(); const renderProduct = config => { const { @@ -93,6 +93,7 @@ const ProductView = ({ t, toolbarGraph, toolbarGraphDescription, useRouteDetail: return ( + {productId !== RHSM_API_PATH_PRODUCT_TYPES.RHOSAK && } @@ -168,7 +169,6 @@ const ProductView = ({ t, toolbarGraph, toolbarGraphDescription, useRouteDetail: {t(`curiosity-view.title`, { appName: helpers.UI_DISPLAY_NAME, context: routeProductLabel })} - {routeProductId !== RHSM_API_PATH_PRODUCT_TYPES.RHOSAK && } {productConfig.map(config => renderProduct(config))} ); diff --git a/src/redux/hooks/__tests__/__snapshots__/useRhsmActions.test.js.snap b/src/redux/hooks/__tests__/__snapshots__/useRhsmActions.test.js.snap deleted file mode 100644 index 9196fe10f..000000000 --- a/src/redux/hooks/__tests__/__snapshots__/useRhsmActions.test.js.snap +++ /dev/null @@ -1,7 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`useRhsmActions should return specific properties: specific properties 1`] = ` -Object { - "useGetMessageReports": [Function], -} -`; diff --git a/src/redux/hooks/__tests__/__snapshots__/useRhsmSelectors.test.js.snap b/src/redux/hooks/__tests__/__snapshots__/useRhsmSelectors.test.js.snap deleted file mode 100644 index 8cc152148..000000000 --- a/src/redux/hooks/__tests__/__snapshots__/useRhsmSelectors.test.js.snap +++ /dev/null @@ -1,16 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`useRhsmSelectors should apply a hook around a selector for useAppMessages: selector 1`] = ` -Object { - "appMessages": Object { - "cloudigradeMismatch": true, - }, - "query": Object {}, -} -`; - -exports[`useRhsmSelectors should return specific properties: specific properties 1`] = ` -Object { - "useAppMessages": [Function], -} -`; diff --git a/src/redux/hooks/__tests__/useRhsmActions.test.js b/src/redux/hooks/__tests__/useRhsmActions.test.js deleted file mode 100644 index 95534787d..000000000 --- a/src/redux/hooks/__tests__/useRhsmActions.test.js +++ /dev/null @@ -1,44 +0,0 @@ -import moxios from 'moxios'; -import { rhsmActionsHooks, useGetMessageReports } from '../useRhsmActions'; -import { store } from '../../store'; -import { rhsmApiTypes } from '../../../types/rhsmApiTypes'; - -describe('useRhsmActions', () => { - beforeEach(() => { - moxios.install(); - - moxios.stubRequest(/\/(tally|capacity|hosts|subscriptions|version).*?/, { - status: 200, - responseText: 'success', - timeout: 1, - response: { - test: 'success', - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA]: ['success'] - } - }); - }); - - afterEach(() => { - moxios.uninstall(); - }); - - it('should return specific properties', () => { - expect(rhsmActionsHooks).toMatchSnapshot('specific properties'); - }); - - it('should apply a hook and receive a response for useGetMessageReports', async () => { - await mountHook(() => useGetMessageReports()); - const { report: nullIdReport } = store.getState().messages; - expect(nullIdReport.fulfilled).toBe(true); - expect(nullIdReport.metaId).toBe(null); - expect(nullIdReport.metaQuery).toMatchObject({}); - - const mockId = 'lorem'; - const mockQuery = { dolor: 'sit' }; - await mountHook(() => useGetMessageReports(mockId, mockQuery)); - const { report: mockIdReport } = store.getState().messages; - expect(mockIdReport[mockId].fulfilled).toBe(true); - expect(mockIdReport[mockId].metaId).toBe(mockId); - expect(mockIdReport[mockId].metaQuery).toMatchObject(mockQuery); - }); -}); diff --git a/src/redux/hooks/__tests__/useRhsmSelectors.test.js b/src/redux/hooks/__tests__/useRhsmSelectors.test.js deleted file mode 100644 index cc35cee8b..000000000 --- a/src/redux/hooks/__tests__/useRhsmSelectors.test.js +++ /dev/null @@ -1,70 +0,0 @@ -import { rhsmSelectorsHooks, useAppMessages } from '../useRhsmSelectors'; -import { rhsmApiTypes } from '../../../types/rhsmApiTypes'; - -describe('useRhsmSelectors', () => { - it('should return specific properties', () => { - expect(rhsmSelectorsHooks).toMatchSnapshot('specific properties'); - }); - - it('should apply a hook around a selector for useAppMessages', () => { - const mockProductId = 'lorem'; - const mockState = { - messages: { - report: { - [mockProductId]: { - fulfilled: true, - data: { - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA]: [ - { - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.DATE]: '2019-09-04T00:00:00.000Z', - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.CORES]: 2, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.SOCKETS]: 2, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.HYPERVISOR_CORES]: 1, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.HYPERVISOR_SOCKETS]: 1, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.PHYSICAL_CORES]: 1, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.PHYSICAL_SOCKETS]: 1, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.HAS_DATA]: true, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.HAS_CLOUDIGRADE_DATA]: true, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.HAS_CLOUDIGRADE_MISMATCH]: true - }, - { - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.DATE]: '2019-09-05T00:00:00.000Z', - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.CORES]: 2, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.SOCKETS]: 2, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.HYPERVISOR_CORES]: 1, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.HYPERVISOR_SOCKETS]: 1, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.PHYSICAL_CORES]: 1, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.PHYSICAL_SOCKETS]: 1, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.HAS_DATA]: true, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.HAS_CLOUDIGRADE_DATA]: true, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.HAS_CLOUDIGRADE_MISMATCH]: true - }, - { - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.DATE]: '2019-09-06T00:00:00.000Z', - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.CORES]: 4, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.SOCKETS]: 4, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.HYPERVISOR_CORES]: 2, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.HYPERVISOR_SOCKETS]: 2, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.PHYSICAL_CORES]: 2, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.PHYSICAL_SOCKETS]: null, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.HAS_DATA]: true, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.HAS_CLOUDIGRADE_DATA]: true, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.HAS_CLOUDIGRADE_MISMATCH]: true - } - ] - } - } - } - } - }; - - const { result } = shallowHook(() => - useAppMessages({ - useRouteDetail: () => ({ pathParameter: mockProductId, productParameter: 'viewIpsum' }), - useSelector: value => value(mockState) - }) - ); - - expect(result).toMatchSnapshot('selector'); - }); -}); diff --git a/src/redux/hooks/index.js b/src/redux/hooks/index.js index a2a18a462..9e44f055d 100644 --- a/src/redux/hooks/index.js +++ b/src/redux/hooks/index.js @@ -1,11 +1,7 @@ import { reactReduxHooks } from './useReactRedux'; -import { rhsmActionsHooks } from './useRhsmActions'; -import { rhsmSelectorsHooks } from './useRhsmSelectors'; const storeHooks = { - reactRedux: reactReduxHooks, - rhsmActions: rhsmActionsHooks, - rhsmSelectors: rhsmSelectorsHooks + reactRedux: reactReduxHooks }; -export { storeHooks as default, storeHooks, reactReduxHooks, rhsmActionsHooks, rhsmSelectorsHooks }; +export { storeHooks as default, storeHooks, reactReduxHooks }; diff --git a/src/redux/hooks/useRhsmActions.js b/src/redux/hooks/useRhsmActions.js deleted file mode 100644 index 6ad3cce17..000000000 --- a/src/redux/hooks/useRhsmActions.js +++ /dev/null @@ -1,27 +0,0 @@ -import { reactReduxHooks } from './useReactRedux'; -import { rhsmTypes } from '../types/rhsmTypes'; -import { rhsmServices } from '../../services/rhsm/rhsmServices'; - -/** - * Get an updated store RHSM response from message reporting. - * - * @param {string} id - * @param {object} query - * @returns {Function} - */ -const useGetMessageReports = (id = null, query = {}) => - reactReduxHooks.useDispatch()({ - type: rhsmTypes.GET_MESSAGE_REPORTS_RHSM, - payload: rhsmServices.getGraphReports(id, query, { cancelId: 'messageReport' }), - meta: { - id, - query, - notifications: {} - } - }); - -const rhsmActionsHooks = { - useGetMessageReports -}; - -export { rhsmActionsHooks as default, rhsmActionsHooks, useGetMessageReports }; diff --git a/src/redux/hooks/useRhsmSelectors.js b/src/redux/hooks/useRhsmSelectors.js deleted file mode 100644 index 52c9a6d04..000000000 --- a/src/redux/hooks/useRhsmSelectors.js +++ /dev/null @@ -1,28 +0,0 @@ -import { useSelector } from 'react-redux'; -import { useRouteDetail } from '../../components/router/routerContext'; -import { reduxSelectors } from '../selectors'; - -/** - * Get app messages selector results. - * - * @param {object} options - * @param {Function} options.useRouteDetail - * @param {Function} options.useSelector - * @returns {object} - */ -const useAppMessages = ({ - useRouteDetail: useAliasRouteDetail = useRouteDetail, - useSelector: useAliasSelector = useSelector -} = {}) => { - const { pathParameter: productId, productParameter: viewId } = useAliasRouteDetail() || {}; - const result = useAliasSelector(state => reduxSelectors.appMessages.appMessages(state, { productId, viewId })); - return { - ...result - }; -}; - -const rhsmSelectorsHooks = { - useAppMessages -}; - -export { rhsmSelectorsHooks as default, rhsmSelectorsHooks, useAppMessages }; diff --git a/src/redux/selectors/__tests__/__snapshots__/appMessagesSelectors.test.js.snap b/src/redux/selectors/__tests__/__snapshots__/appMessagesSelectors.test.js.snap deleted file mode 100644 index 61481ee1c..000000000 --- a/src/redux/selectors/__tests__/__snapshots__/appMessagesSelectors.test.js.snap +++ /dev/null @@ -1,71 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`AppMessagesSelectors should map a fulfilled product ID response to an aggregated output: fulfilled 1`] = ` -Object { - "appMessages": Object { - "cloudigradeMismatch": true, - }, - "query": Object {}, -} -`; - -exports[`AppMessagesSelectors should pass minimal data on a product ID without a product ID provided: no product id error 1`] = ` -Object { - "appMessages": Object { - "cloudigradeMismatch": false, - }, - "query": Object {}, -} -`; - -exports[`AppMessagesSelectors should pass minimal data on missing a reducer response: missing reducer error 1`] = ` -Object { - "appMessages": Object { - "cloudigradeMismatch": false, - }, - "query": Object {}, -} -`; - -exports[`AppMessagesSelectors should populate data from the in memory cache: cached data: cache used and pending 1`] = ` -Object { - "appMessages": Object { - "cloudigradeMismatch": true, - }, - "query": Object {}, -} -`; - -exports[`AppMessagesSelectors should populate data from the in memory cache: cached data: initial fulfilled 1`] = ` -Object { - "appMessages": Object { - "cloudigradeMismatch": true, - }, - "query": Object {}, -} -`; - -exports[`AppMessagesSelectors should populate data from the in memory cache: cached data: update and fulfilled 1`] = ` -Object { - "appMessages": Object { - "cloudigradeMismatch": true, - }, - "query": Object {}, -} -`; - -exports[`AppMessagesSelectors should populate data on a product ID when the api response is missing expected properties: data populated, missing properties 1`] = ` -Object { - "appMessages": Object { - "cloudigradeMismatch": false, - }, - "query": Object {}, -} -`; - -exports[`AppMessagesSelectors should return specific selectors: selectors 1`] = ` -Object { - "appMessages": [Function], - "makeAppMessages": [Function], -} -`; diff --git a/src/redux/selectors/__tests__/appMessagesSelectors.test.js b/src/redux/selectors/__tests__/appMessagesSelectors.test.js deleted file mode 100644 index 43987259c..000000000 --- a/src/redux/selectors/__tests__/appMessagesSelectors.test.js +++ /dev/null @@ -1,207 +0,0 @@ -import appMessagesSelectors from '../appMessagesSelectors'; -import { rhsmApiTypes } from '../../../types/rhsmApiTypes'; - -describe('AppMessagesSelectors', () => { - it('should return specific selectors', () => { - expect(appMessagesSelectors).toMatchSnapshot('selectors'); - }); - - it('should pass minimal data on missing a reducer response', () => { - const state = {}; - expect(appMessagesSelectors.appMessages(state)).toMatchSnapshot('missing reducer error'); - }); - - it('should pass minimal data on a product ID without a product ID provided', () => { - const props = { - viewId: 'test', - productId: undefined - }; - const state = { - messages: { - report: { - fulfilled: true, - data: { [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA]: [] } - } - } - }; - - expect(appMessagesSelectors.appMessages(state, props)).toMatchSnapshot('no product id error'); - }); - - it('should populate data on a product ID when the api response is missing expected properties', () => { - const props = { - viewId: 'test', - productId: 'Lorem Ipsum missing expected properties' - }; - const state = { - messages: { - report: { - 'Lorem Ipsum missing expected properties': { - fulfilled: true, - data: { - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA]: [ - { - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.DATE]: '2019-09-04T00:00:00.000Z', - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.SOCKETS]: 2, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.PHYSICAL_SOCKETS]: 1 - }, - { - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.DATE]: '2019-09-05T00:00:00.000Z', - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.SOCKETS]: 2, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.HYPERVISOR_SOCKETS]: 1 - }, - { - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.DATE]: '2019-09-06T00:00:00.000Z', - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.SOCKETS]: 4, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.PHYSICAL_SOCKETS]: 2 - } - ] - } - } - } - } - }; - - expect(appMessagesSelectors.appMessages(state, props)).toMatchSnapshot('data populated, missing properties'); - }); - - it('should map a fulfilled product ID response to an aggregated output', () => { - const props = { - viewId: 'test', - productId: 'Lorem Ipsum fulfilled aggregated output' - }; - const state = { - messages: { - report: { - 'Lorem Ipsum fulfilled aggregated output': { - fulfilled: true, - data: { - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA]: [ - { - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.DATE]: '2019-09-04T00:00:00.000Z', - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.CORES]: 2, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.SOCKETS]: 2, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.HYPERVISOR_CORES]: 1, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.HYPERVISOR_SOCKETS]: 1, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.PHYSICAL_CORES]: 1, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.PHYSICAL_SOCKETS]: 1, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.HAS_DATA]: true, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.HAS_CLOUDIGRADE_DATA]: true, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.HAS_CLOUDIGRADE_MISMATCH]: true - }, - { - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.DATE]: '2019-09-05T00:00:00.000Z', - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.CORES]: 2, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.SOCKETS]: 2, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.HYPERVISOR_CORES]: 1, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.HYPERVISOR_SOCKETS]: 1, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.PHYSICAL_CORES]: 1, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.PHYSICAL_SOCKETS]: 1, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.HAS_DATA]: true, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.HAS_CLOUDIGRADE_DATA]: true, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.HAS_CLOUDIGRADE_MISMATCH]: true - }, - { - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.DATE]: '2019-09-06T00:00:00.000Z', - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.CORES]: 4, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.SOCKETS]: 4, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.HYPERVISOR_CORES]: 2, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.HYPERVISOR_SOCKETS]: 2, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.PHYSICAL_CORES]: 2, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.PHYSICAL_SOCKETS]: null, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.HAS_DATA]: true, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.HAS_CLOUDIGRADE_DATA]: true, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.HAS_CLOUDIGRADE_MISMATCH]: true - } - ] - } - } - } - } - }; - - expect(appMessagesSelectors.appMessages(state, props)).toMatchSnapshot('fulfilled'); - }); - - it('should populate data from the in memory cache', () => { - const props = { - viewId: 'cache-test', - productId: 'Lorem Ipsum ID cached' - }; - const stateInitialFulfilled = { - messages: { - report: { - 'Lorem Ipsum ID cached': { - fulfilled: true, - data: { - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA]: [ - { - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.DATE]: '2019-09-04T00:00:00.000Z', - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.CORES]: 2, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.SOCKETS]: 2, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.HYPERVISOR_CORES]: 1, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.HYPERVISOR_SOCKETS]: 1, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.PHYSICAL_CORES]: 1, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.PHYSICAL_SOCKETS]: 1, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.HAS_DATA]: true, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.HAS_CLOUDIGRADE_DATA]: true, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.HAS_CLOUDIGRADE_MISMATCH]: true - } - ] - } - } - } - } - }; - - expect(appMessagesSelectors.appMessages(stateInitialFulfilled, props)).toMatchSnapshot( - 'cached data: initial fulfilled' - ); - - const statePending = { - messages: { - report: { - 'Lorem Ipsum ID cached': { - ...stateInitialFulfilled.messages.report['Lorem Ipsum ID cached'], - pending: true - } - } - } - }; - - expect(appMessagesSelectors.appMessages(statePending, props)).toMatchSnapshot( - 'cached data: cache used and pending' - ); - - const stateFulfilled = { - messages: { - report: { - 'Lorem Ipsum ID cached': { - ...stateInitialFulfilled.messages.report['Lorem Ipsum ID cached'], - fulfilled: true, - data: { - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA]: [ - { - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.DATE]: '2018-07-04T00:00:00.000Z', - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.CORES]: 2, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.SOCKETS]: 2, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.HYPERVISOR_CORES]: 1, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.HYPERVISOR_SOCKETS]: 1, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.PHYSICAL_CORES]: 1, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.PHYSICAL_SOCKETS]: 1, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.HAS_DATA]: true, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.HAS_CLOUDIGRADE_DATA]: true, - [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.HAS_CLOUDIGRADE_MISMATCH]: false - } - ] - } - } - } - } - }; - - expect(appMessagesSelectors.appMessages(stateFulfilled, props)).toMatchSnapshot( - 'cached data: update and fulfilled' - ); - }); -}); diff --git a/src/redux/selectors/appMessagesSelectors.js b/src/redux/selectors/appMessagesSelectors.js deleted file mode 100644 index c556976c3..000000000 --- a/src/redux/selectors/appMessagesSelectors.js +++ /dev/null @@ -1,90 +0,0 @@ -import { createSelector } from 'reselect'; -import { rhsmApiTypes } from '../../types'; - -/** - * Selector cache. - * - * @private - * @type {{data: {object}}} - */ -const selectorCache = { data: {} }; - -/** - * Return a combined state, props object. - * - * @private - * @param {object} state - * @param {object} props - * @returns {object} - */ -const statePropsFilter = (state, props = {}) => ({ - report: state.messages?.report?.[props.productId], - viewId: props.viewId, - productId: props.productId -}); - -/** - * Return a combined query object. - * - * @param {object} state - * @param {object} props - * @returns {object} - */ -const queryFilter = (state, props = {}) => ({ - ...props.query, - ...state.view?.query?.[props.productId], - ...state.view?.query?.[props.viewId] -}); - -/** - * Create selector, transform combined state, props into a consumable object. - * - * @type {{appMessages: {cloudigradeMismatch: boolean}}} - */ -const selector = createSelector([statePropsFilter, queryFilter], (data, query = {}) => { - const { viewId = null, productId = null, report = {} } = data || {}; - const appMessages = { - cloudigradeMismatch: false - }; - - const cache = (viewId && productId && selectorCache.data[`${viewId}_${productId}`]) || undefined; - - Object.assign(appMessages, { ...cache }); - - // Scan Tally response for Cloud Meter flags - if (report.fulfilled && appMessages.cloudigradeMismatch !== true) { - const { [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA]: reportData = [] } = report.data || {}; - - const cloudigradeMismatch = reportData - .reverse() - .find( - ({ [rhsmApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.HAS_CLOUDIGRADE_MISMATCH]: mismatch }) => - mismatch === true - ); - - appMessages.cloudigradeMismatch = cloudigradeMismatch !== undefined; - - selectorCache.data[`${viewId}_${productId}`] = { - ...appMessages - }; - } - - return { appMessages, query }; -}); - -/** - * Expose selector instance. For scenarios where a selector is reused across component instances. - * - * @param {object} defaultProps - * @returns {{appMessages: {cloudigradeMismatch: boolean}}} - */ -const makeSelector = defaultProps => (state, props) => ({ - ...selector(state, props, defaultProps) -}); - -const appMessagesSelectors = { - appMessages: selector, - makeAppMessages: makeSelector -}; - -export { appMessagesSelectors as default, appMessagesSelectors, selector, makeSelector }; diff --git a/src/redux/selectors/index.js b/src/redux/selectors/index.js index 53a620d95..debc19620 100644 --- a/src/redux/selectors/index.js +++ b/src/redux/selectors/index.js @@ -1,11 +1,9 @@ -import appMessagesSelectors from './appMessagesSelectors'; import guestsListSelectors from './guestsListSelectors'; import graphCardSelectors from './graphCardSelectors'; import inventoryListSelectors from './inventoryListSelectors'; import userSelectors from './userSelectors'; const reduxSelectors = { - appMessages: appMessagesSelectors, guestsList: guestsListSelectors, graphCard: graphCardSelectors, inventoryList: inventoryListSelectors, diff --git a/src/services/rhsm/__tests__/__snapshots__/rhsmTransformers.test.js.snap b/src/services/rhsm/__tests__/__snapshots__/rhsmTransformers.test.js.snap index 2be437bc2..520055d4c 100644 --- a/src/services/rhsm/__tests__/__snapshots__/rhsmTransformers.test.js.snap +++ b/src/services/rhsm/__tests__/__snapshots__/rhsmTransformers.test.js.snap @@ -4,6 +4,7 @@ exports[`RHSM Transformers should attempt to parse a tally response: tally 1`] = Object { "data": Array [], "meta": Object { + "cloudigradeHasMismatch": undefined, "count": undefined, "metricId": undefined, "productId": undefined, diff --git a/src/services/rhsm/rhsmTransformers.js b/src/services/rhsm/rhsmTransformers.js index d703c41c7..a4658a177 100644 --- a/src/services/rhsm/rhsmTransformers.js +++ b/src/services/rhsm/rhsmTransformers.js @@ -79,6 +79,7 @@ const rhsmTally = response => { updatedResponse.meta = { count: meta[TALLY_META_TYPES.COUNT], + cloudigradeHasMismatch: meta[TALLY_META_TYPES.HAS_CLOUDIGRADE_MISMATCH], metricId: meta[TALLY_META_TYPES.METRIC_ID], productId: meta[TALLY_META_TYPES.PRODUCT], totalMonthlyDate: meta[TALLY_META_TYPES.TOTAL_MONTHLY]?.[TALLY_META_TYPES.DATE], diff --git a/tests/__snapshots__/dist.test.js.snap b/tests/__snapshots__/dist.test.js.snap index 0c4673cfc..21c216b4e 100644 --- a/tests/__snapshots__/dist.test.js.snap +++ b/tests/__snapshots__/dist.test.js.snap @@ -25,6 +25,7 @@ Array [ "./dist/js/1355*js", "./dist/js/136*js", "./dist/js/1799*js", + "./dist/js/1824*js", "./dist/js/1858*js", "./dist/js/190*js", "./dist/js/2130*js", @@ -110,6 +111,7 @@ Array [ "./dist/sourcemaps/1355*map", "./dist/sourcemaps/136*map", "./dist/sourcemaps/1799*map", + "./dist/sourcemaps/1824*map", "./dist/sourcemaps/1858*map", "./dist/sourcemaps/190*map", "./dist/sourcemaps/2130*map",