From 252afc1bb2953402bf41ccc2dec6c2a6f09886de Mon Sep 17 00:00:00 2001 From: CD Cabrera Date: Tue, 24 Sep 2019 12:15:16 -0400 Subject: [PATCH] feat(rhelGraphCard): issues/80 activate threshold api (#108) * build, dotenv add version and capacity endpoints * types, api and reducer level types for api capacity * chartArea, fix area chart default color * graphHelpers, index plus date match check, threshold check * rhelGraphCard testing updates * rhelActions, rhelGraphReducer, naming updates, streamline * reduxHelpers, fix error messaging * graphCardSelectors, aggregate multiple api responses * rhelServices, add capacity api get, rename towards RHEL * userServices, add api version get --- .env | 2 + .env.development | 2 + .env.proxy | 2 + .../__snapshots__/graphHelpers.test.js.snap | 194 ++++--- src/common/__tests__/graphHelpers.test.js | 242 ++++---- src/common/graphHelpers.js | 31 +- .../__snapshots__/chartArea.test.js.snap | 132 +++-- .../chartArea/__tests__/chartArea.test.js | 22 +- src/components/chartArea/chartArea.js | 64 ++- .../__snapshots__/rhelGraphCard.test.js.snap | 536 ++++++++++-------- .../__tests__/rhelGraphCard.test.js | 28 +- src/components/rhelGraphCard/rhelGraphCard.js | 65 ++- .../actions/__tests__/rhelActions.test.js | 18 +- src/redux/actions/rhelActions.js | 29 +- .../__snapshots__/reduxHelpers.test.js.snap | 1 + src/redux/common/reduxHelpers.js | 77 +++ .../rhelGraphReducer.test.js.snap | 151 ++++- .../__tests__/rhelGraphReducer.test.js | 20 +- src/redux/reducers/rhelGraphReducer.js | 64 +-- .../graphCardSelectors.test.js.snap | 99 ++++ .../__tests__/graphCardSelectors.test.js | 135 +++++ src/redux/selectors/graphCardSelectors.js | 65 +++ src/redux/selectors/index.js | 6 +- .../__snapshots__/index.test.js.snap | 12 +- src/redux/types/rhelTypes.js | 5 +- src/services/__tests__/rhelServices.test.js | 11 +- src/services/__tests__/userServices.test.js | 22 +- src/services/rhelServices.js | 191 ++++++- src/services/userServices.js | 45 +- .../__snapshots__/rhelApiTypes.test.js.snap | 104 +++- src/types/rhelApiTypes.js | 51 +- tests/__snapshots__/i18n.test.js.snap | 14 +- 32 files changed, 1710 insertions(+), 730 deletions(-) create mode 100644 src/redux/selectors/__tests__/__snapshots__/graphCardSelectors.test.js.snap create mode 100644 src/redux/selectors/__tests__/graphCardSelectors.test.js create mode 100644 src/redux/selectors/graphCardSelectors.js diff --git a/.env b/.env index b96d105ff..d21e189ad 100644 --- a/.env +++ b/.env @@ -19,4 +19,6 @@ REACT_APP_CONFIG_SERVICE_LOCALES_EXPIRE=604800000 REACT_APP_INCLUDE_CONTENT_HEADER= REACT_APP_INCLUDE_CONTENT_BODY= +REACT_APP_SERVICES_RHSM_VERSION=/api/rhsm-subscriptions/v1/version REACT_APP_SERVICES_RHSM_REPORT_RHEL=/api/rhsm-subscriptions/v1/tally/products/RHEL +REACT_APP_SERVICES_RHSM_CAPACITY_RHEL=/api/rhsm-subscriptions/v1/capacity/products/RHEL diff --git a/.env.development b/.env.development index be2978866..13ae93985 100644 --- a/.env.development +++ b/.env.development @@ -6,4 +6,6 @@ REACT_APP_INCLUDE_CONTENT_BODY=
REACT_APP_CONFIG_SERVICE_LOCALES=./locales/locales.json REACT_APP_CONFIG_SERVICE_LOCALES_PATH=./locales/{{lng}}.json +REACT_APP_SERVICES_RHSM_VERSION=http://localhost:5000/api/rhsm-subscriptions/v1/version REACT_APP_SERVICES_RHSM_REPORT_RHEL=http://localhost:5000/api/rhsm-subscriptions/v1/tally/products/RHEL +REACT_APP_SERVICES_RHSM_CAPACITY_RHEL=http://localhost:5000/api/rhsm-subscriptions/v1/capacity/products/RHEL diff --git a/.env.proxy b/.env.proxy index 38ea2f3b0..edf31d342 100644 --- a/.env.proxy +++ b/.env.proxy @@ -10,4 +10,6 @@ REACT_APP_INCLUDE_CONTENT_BODY=' { it('should convert graph data and return zeroed usage array if error', () => { const props = { - dataFacet: rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS, - dataThresholdFacet: rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS_THRESHOLD, + dataFacet: rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.SOCKETS, tooltipLabel: 'lorem tooltip label', tooltipThresholdLabel: 'ipsum threshhold label', granularity: GRANULARITY_TYPES.DAILY, @@ -30,14 +29,15 @@ describe('GraphHelpers', () => { it('should convert graph data and return zeroed usage array if usage is empty', () => { const props = { - dataFacet: rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS, - dataThresholdFacet: rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS_THRESHOLD, + data: [], + dataFacet: rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.SOCKETS, + dataThreshold: [], + dataThresholdFacet: rhelApiTypes.RHSM_API_RESPONSE_CAPACITY_DATA_TYPES.PHYSICAL_SOCKETS, tooltipLabel: 'lorem tooltip label', tooltipThresholdLabel: 'ipsum threshhold label', granularity: GRANULARITY_TYPES.DAILY, startDate: new Date('2019-06-01T00:00:00Z'), - endDate: new Date('2019-06-05T23:59:59Z'), - data: [] + endDate: new Date('2019-06-05T23:59:59Z') }; expect(convertChartData(props)).toMatchSnapshot('zeroed array'); @@ -45,48 +45,41 @@ describe('GraphHelpers', () => { it('should convert graph data and generate tooltips when usage is populated', () => { const props = { - dataFacet: rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS, - dataThresholdFacet: rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS_THRESHOLD, + dataFacet: rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.SOCKETS, tooltipLabel: 'lorem tooltip label', - tooltipThresholdLabel: 'ipsum threshhold label', granularity: GRANULARITY_TYPES.DAILY, startDate: new Date('2019-06-01T00:00:00Z'), endDate: new Date('2019-06-05T23:59:59Z'), data: [ { - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_CORES]: 56, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_DATE]: '2019-06-01T00:00:00Z', - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_INSTANCES]: 28, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS]: 5, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS_THRESHOLD]: 10 + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.CORES]: 56, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.DATE]: '2019-06-01T00:00:00Z', + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.INSTANCES]: 28, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.SOCKETS]: 5 }, { - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_CORES]: 30, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_DATE]: '2019-06-02T00:00:00Z', - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_INSTANCES]: 28, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS]: 7, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS_THRESHOLD]: 10 + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.CORES]: 30, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.DATE]: '2019-06-02T00:00:00Z', + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.INSTANCES]: 28, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.SOCKETS]: 7 }, { - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_CORES]: 40, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_DATE]: '2019-06-03T00:00:00Z', - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_INSTANCES]: 28, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS]: 3, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS_THRESHOLD]: 10 + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.CORES]: 40, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.DATE]: '2019-06-03T00:00:00Z', + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.INSTANCES]: 28, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.SOCKETS]: 3 }, { - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_CORES]: 0, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_DATE]: '2019-06-04T00:00:00Z', - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_INSTANCES]: 0, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS]: 0, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS_THRESHOLD]: 10 + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.CORES]: 0, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.DATE]: '2019-06-04T00:00:00Z', + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.INSTANCES]: 0, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.SOCKETS]: 0 }, { - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_CORES]: 0, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_DATE]: '2019-06-05T00:00:00Z', - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_INSTANCES]: 0, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS]: 0, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS_THRESHOLD]: 10 + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.CORES]: 0, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.DATE]: '2019-06-05T00:00:00Z', + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.INSTANCES]: 0, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.SOCKETS]: 0 } ] }; @@ -94,15 +87,74 @@ describe('GraphHelpers', () => { expect(convertChartData(props)).toMatchSnapshot('usage populated'); }); - it('should convert graph data and returned zeroed array when usage throws error', () => { + it('should convert graph data and threshold data', () => { const props = { - dataFacet: rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS, - dataThresholdFacet: rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS_THRESHOLD, + dataFacet: rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.SOCKETS, + dataThresholdFacet: rhelApiTypes.RHSM_API_RESPONSE_CAPACITY_DATA_TYPES.PHYSICAL_SOCKETS, tooltipLabel: 'lorem tooltip label', tooltipThresholdLabel: 'ipsum threshhold label', granularity: GRANULARITY_TYPES.DAILY, startDate: new Date('2019-06-01T00:00:00Z'), endDate: new Date('2019-06-05T23:59:59Z'), + data: [ + { + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.CORES]: 56, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.DATE]: '2019-06-01T00:00:00Z', + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.INSTANCES]: 28, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.SOCKETS]: 5 + }, + { + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.CORES]: 30, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.DATE]: '2019-06-02T00:00:00Z', + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.INSTANCES]: 28, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.SOCKETS]: 7 + }, + { + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.CORES]: 40, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.DATE]: '2019-06-03T00:00:00Z', + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.INSTANCES]: 28, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.SOCKETS]: 3 + }, + { + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.CORES]: 0, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.DATE]: '2019-06-04T00:00:00Z', + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.INSTANCES]: 0, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.SOCKETS]: 0 + }, + { + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.CORES]: 0, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.DATE]: '2019-06-05T00:00:00Z', + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.INSTANCES]: 0, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.SOCKETS]: 1 + } + ], + dataThreshold: [ + { + [rhelApiTypes.RHSM_API_RESPONSE_CAPACITY_DATA_TYPES.DATE]: '2019-06-01T00:00:00Z', + [rhelApiTypes.RHSM_API_RESPONSE_CAPACITY_DATA_TYPES.PHYSICAL_SOCKETS]: 100 + } + ] + }; + + expect(convertChartData(props)).toMatchSnapshot('threshold check'); + + props.dataThreshold = [ + { + [rhelApiTypes.RHSM_API_RESPONSE_CAPACITY_DATA_TYPES.DATE]: '2019-06-05T00:00:00Z', + [rhelApiTypes.RHSM_API_RESPONSE_CAPACITY_DATA_TYPES.PHYSICAL_SOCKETS]: 100 + } + ]; + + expect(convertChartData(props)).toMatchSnapshot('threshold check date and index mismatch from data'); + }); + + it('should convert graph data and returned zeroed array when usage throws error', () => { + const props = { + dataFacet: rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.SOCKETS, + tooltipLabel: 'lorem tooltip label', + granularity: GRANULARITY_TYPES.DAILY, + startDate: new Date('2019-06-01T00:00:00Z'), + endDate: new Date('2019-06-05T23:59:59Z'), data: [null] }; @@ -115,62 +167,53 @@ describe('GraphHelpers', () => { it('should handle cross year labels', () => { const props = { - dataFacet: rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS, - dataThresholdFacet: rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS_THRESHOLD, + dataFacet: rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.SOCKETS, tooltipLabel: 'lorem tooltip label', - tooltipThresholdLabel: 'ipsum threshhold label', granularity: GRANULARITY_TYPES.DAILY, startDate: new Date('2018-12-31T00:00:00Z'), endDate: new Date('2019-01-06T00:00:00Z'), data: [ { - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_CORES]: 56, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_DATE]: '2018-12-31T00:00:00Z', - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_INSTANCES]: 28, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS]: 5, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS_THRESHOLD]: 10 + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.CORES]: 56, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.DATE]: '2018-12-31T00:00:00Z', + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.INSTANCES]: 28, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.SOCKETS]: 5 }, { - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_CORES]: 30, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_DATE]: '2019-01-01T00:00:00Z', - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_INSTANCES]: 28, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS]: 7, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS_THRESHOLD]: 10 + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.CORES]: 30, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.DATE]: '2019-01-01T00:00:00Z', + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.INSTANCES]: 28, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.SOCKETS]: 7 }, { - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_CORES]: 30, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_DATE]: '2019-01-02T00:00:00Z', - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_INSTANCES]: 28, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS]: 7, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS_THRESHOLD]: 10 + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.CORES]: 30, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.DATE]: '2019-01-02T00:00:00Z', + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.INSTANCES]: 28, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.SOCKETS]: 7 }, { - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_CORES]: 30, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_DATE]: '2019-01-03T00:00:00Z', - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_INSTANCES]: 28, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS]: 7, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS_THRESHOLD]: 10 + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.CORES]: 30, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.DATE]: '2019-01-03T00:00:00Z', + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.INSTANCES]: 28, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.SOCKETS]: 7 }, { - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_CORES]: 30, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_DATE]: '2019-01-04T00:00:00Z', - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_INSTANCES]: 28, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS]: 7, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS_THRESHOLD]: 10 + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.CORES]: 30, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.DATE]: '2019-01-04T00:00:00Z', + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.INSTANCES]: 28, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.SOCKETS]: 7 }, { - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_CORES]: 30, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_DATE]: '2019-01-05T00:00:00Z', - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_INSTANCES]: 28, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS]: 7, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS_THRESHOLD]: 10 + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.CORES]: 30, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.DATE]: '2019-01-05T00:00:00Z', + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.INSTANCES]: 28, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.SOCKETS]: 7 }, { - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_CORES]: 30, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_DATE]: '2019-01-06T00:00:00Z', - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_INSTANCES]: 28, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS]: 7, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS_THRESHOLD]: 10 + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.CORES]: 30, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.DATE]: '2019-01-06T00:00:00Z', + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.INSTANCES]: 28, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.SOCKETS]: 7 } ] }; @@ -180,48 +223,41 @@ describe('GraphHelpers', () => { it('should handle cross quarter labels', () => { const props = { - dataFacet: rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS, - dataThresholdFacet: rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS_THRESHOLD, + dataFacet: rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.SOCKETS, tooltipLabel: 'lorem tooltip label', - tooltipThresholdLabel: 'ipsum threshhold label', granularity: GRANULARITY_TYPES.QUARTERLY, startDate: new Date('2018-04-01T00:00:00Z'), endDate: new Date('2019-04-01T00:00:00Z'), data: [ { - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_CORES]: 56, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_DATE]: '2018-04-01T00:00:00Z', - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_INSTANCES]: 28, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS]: 5, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS_THRESHOLD]: 10 + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.CORES]: 56, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.DATE]: '2018-04-01T00:00:00Z', + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.INSTANCES]: 28, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.SOCKETS]: 5 }, { - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_CORES]: 30, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_DATE]: '2018-08-01T00:00:00Z', - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_INSTANCES]: 28, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS]: 7, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS_THRESHOLD]: 10 + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.CORES]: 30, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.DATE]: '2018-08-01T00:00:00Z', + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.INSTANCES]: 28, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.SOCKETS]: 7 }, { - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_CORES]: 30, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_DATE]: '2018-12-01T00:00:00Z', - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_INSTANCES]: 28, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS]: 7, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS_THRESHOLD]: 10 + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.CORES]: 30, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.DATE]: '2018-12-01T00:00:00Z', + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.INSTANCES]: 28, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.SOCKETS]: 7 }, { - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_CORES]: 30, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_DATE]: '2019-04-01T00:00:00Z', - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_INSTANCES]: 28, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS]: 7, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS_THRESHOLD]: 10 + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.CORES]: 30, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.DATE]: '2019-04-01T00:00:00Z', + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.INSTANCES]: 28, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.SOCKETS]: 7 }, { - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_CORES]: 30, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_DATE]: '2019-08-01T00:00:00Z', - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_INSTANCES]: 28, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS]: 7, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS_THRESHOLD]: 10 + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.CORES]: 30, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.DATE]: '2019-08-01T00:00:00Z', + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.INSTANCES]: 28, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.SOCKETS]: 7 } ] }; diff --git a/src/common/graphHelpers.js b/src/common/graphHelpers.js index ccd038c61..23e44c0cb 100644 --- a/src/common/graphHelpers.js +++ b/src/common/graphHelpers.js @@ -210,8 +210,9 @@ const fillFormatChartData = ({ data, endDate, granularity, startDate, tooltipLab /** * Convert graph data to consumable format * - * @param {Array} data + * @param {Array} data list of available report data * @param {string} dataFacet the response property used for the y axis + * @param {Array} dataThreshold list of available capacity data * @param {string} dataThresholdFacet the response property for the threshold line indicator * @param {string} tooltipLabel the tooltip label * @param {string} tooltipThresholdLabel the tooltip threshold label @@ -223,6 +224,7 @@ const fillFormatChartData = ({ data, endDate, granularity, startDate, tooltipLab const convertChartData = ({ data, dataFacet, + dataThreshold, dataThresholdFacet, tooltipLabel, tooltipThresholdLabel, @@ -232,15 +234,36 @@ const convertChartData = ({ }) => { const formattedData = {}; - (data || []).forEach(value => { + (data || []).forEach((value, index) => { if (value) { const stringDate = moment - .utc(value[rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_DATE]) + .utc(value[rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.DATE]) .startOf('day') .toISOString(); + + /** + * FixMe: per API the list indexes should match on capacity and reporting.Once resonable + * Once reasonable testing has occurred consider removing this check. + */ + const checkThresholdDate = dataThresholdItem => { + if (!dataThresholdItem) { + return false; + } + + const stringThresholdDate = moment + .utc(dataThresholdItem[rhelApiTypes.RHSM_API_RESPONSE_CAPACITY_DATA_TYPES.DATE]) + .startOf('day') + .toISOString(); + + return moment(stringDate).isSame(stringThresholdDate); + }; + formattedData[stringDate] = { data: Number.parseInt(value[dataFacet], 10), - dataThreshold: Number.parseInt(value[dataThresholdFacet], 10) + dataThreshold: Number.parseInt( + (checkThresholdDate(dataThreshold && dataThreshold[index]) && dataThreshold[index][dataThresholdFacet]) || 0, + 10 + ) }; } }); diff --git a/src/components/chartArea/__tests__/__snapshots__/chartArea.test.js.snap b/src/components/chartArea/__tests__/__snapshots__/chartArea.test.js.snap index 77c1fb2cb..c9adc719e 100644 --- a/src/components/chartArea/__tests__/__snapshots__/chartArea.test.js.snap +++ b/src/components/chartArea/__tests__/__snapshots__/chartArea.test.js.snap @@ -346,25 +346,25 @@ exports[`ChartArea Component should allow tick formatting: y tick format 1`] = ` y="257" /> + - @@ -612,12 +629,13 @@ Object { "legendData": Array [ Object { "name": "Arma virumque cano", - "symbol": Object { - "type": "threshold", - }, + "symbol": Object {}, }, Object { "name": "Arma virumque cano", + "symbol": Object { + "type": "threshold", + }, }, ], "legendOrientation": "horizontal", @@ -671,6 +689,11 @@ Object { exports[`ChartArea Component should handle variation in passed properties with specific methods: variation 1`] = `
@@ -801,41 +836,10 @@ exports[`ChartArea Component should handle variation in passed properties with s exports[`ChartArea Component should render a basic component: basic 1`] = `
} - role="presentation" - shapeRendering="auto" - /> - } - groupComponent={} - labelComponent={ - } - tspanComponent={} - /> - } - renderInPortal={true} - /> - } - labels={[Function]} - portalComponent={} - portalZIndex={99} - responsive={true} - voronoiPadding={5} - /> + animate={ + Object { + "duration": 0, + } } domain={ Object { @@ -872,9 +876,11 @@ exports[`ChartArea Component should render a basic component: basic 1`] = ` width={0} > diff --git a/src/components/chartArea/__tests__/chartArea.test.js b/src/components/chartArea/__tests__/chartArea.test.js index 40d2ce211..887901ff3 100644 --- a/src/components/chartArea/__tests__/chartArea.test.js +++ b/src/components/chartArea/__tests__/chartArea.test.js @@ -30,7 +30,7 @@ describe('ChartArea Component', () => { yAxisLabel: '2 y axis label' } ], - legendData: { name: 'Arma virumque cano' } + dataLegendLabel: 'Arma virumque cano' } ] }; @@ -58,8 +58,8 @@ describe('ChartArea Component', () => { xAxisLabel: '2 x axis label' } ], - legendData: { name: 'Arma virumque cano' }, - legendThreshold: { name: 'Arma virumque cano' } + dataLegendLabel: 'Arma virumque cano', + thresholdLegendLabel: 'Arma virumque cano' } ] }; @@ -93,8 +93,8 @@ describe('ChartArea Component', () => { xAxisLabel: '2 x axis label' } ], - legendData: { name: 'Arma virumque cano' }, - legendThreshold: { name: 'Arma virumque cano' } + dataLegendLabel: 'Arma virumque cano', + thresholdLegendLabel: 'Arma virumque cano' } ] }; @@ -125,7 +125,7 @@ describe('ChartArea Component', () => { xAxisLabel: '2 x axis label' } ], - thresholds: [ + threshold: [ { x: 1, y: 10, @@ -139,8 +139,8 @@ describe('ChartArea Component', () => { xAxisLabel: '2 x axis label' } ], - legendData: { name: 'Arma virumque cano' }, - legendThreshold: { name: 'Arma virumque cano' } + dataLegendLabel: 'Arma virumque cano', + thresholdLegendLabel: 'Arma virumque cano' } ] }; @@ -161,7 +161,7 @@ describe('ChartArea Component', () => { xAxisLabel: '1 x axis label' } ], - thresholds: [ + threshold: [ { x: 1, y: 10, @@ -170,8 +170,8 @@ describe('ChartArea Component', () => { } ], dataInterpolation: 'natural', - legendData: { name: 'Arma virumque cano' }, - legendThreshold: { name: 'Arma virumque cano' } + dataLegendLabel: 'Arma virumque cano', + thresholdLegendLabel: 'Arma virumque cano' } ] }; diff --git a/src/components/chartArea/chartArea.js b/src/components/chartArea/chartArea.js index 185360209..12de33cca 100644 --- a/src/components/chartArea/chartArea.js +++ b/src/components/chartArea/chartArea.js @@ -140,8 +140,8 @@ class ChartArea extends React.Component { dataSetMaxY = value.y > dataSetMaxY ? value.y : dataSetMaxY; }); - if (dataSet.thresholds) { - dataSet.thresholds.forEach(value => { + if (dataSet.threshold) { + dataSet.threshold.forEach(value => { dataSetMaxY = value.y > dataSetMaxY ? value.y : dataSetMaxY; }); } @@ -171,12 +171,24 @@ class ChartArea extends React.Component { const legendData = []; dataSets.forEach(dataSet => { - if (dataSet.legendThreshold) { - legendData.push({ symbol: { type: 'threshold' }, ...dataSet.legendThreshold }); + if (dataSet.dataLegendLabel) { + const legendDataSettings = { symbol: {}, name: dataSet.dataLegendLabel }; + + if (dataSet.dataColor) { + legendDataSettings.symbol.fill = dataSet.dataColor; + } + + legendData.push(legendDataSettings); } - if (dataSet.legendData) { - legendData.push(dataSet.legendData); + if (dataSet.thresholdLegendLabel) { + const legendThresholdSettings = { symbol: { type: 'threshold' }, name: dataSet.thresholdLegendLabel }; + + if (dataSet.thresholdColor) { + legendThresholdSettings.symbol.fill = dataSet.thresholdColor; + } + + legendData.push(legendThresholdSettings); } }); @@ -193,29 +205,33 @@ class ChartArea extends React.Component { const { dataSets, padding } = this.props; const { isXAxisTicks, isYAxisTicks, xAxisProps, yAxisProps } = this.getChartTicks(); - const { chartDomain } = this.getChartDomain({ isXAxisTicks, isYAxisTicks }); + const { chartDomain, maxY } = this.getChartDomain({ isXAxisTicks, isYAxisTicks }); const chartLegendProps = this.getChartLegend(); const chartProps = { padding, ...chartLegendProps, ...chartDomain }; - chartProps.containerComponent = ( - datum.tooltip} /> - ); + if (maxY > 0) { + chartProps.containerComponent = ( + datum.tooltip} /> + ); + } return (
- - - + + + {(dataSets && dataSets.length && dataSets.map( dataSet => - (dataSet.thresholds && dataSet.thresholds.length && ( - /** fixme: split this out into a new wrapper called ChartThreshold in PF React */ + (dataSet.threshold && dataSet.threshold.length && ( )) || @@ -229,9 +245,12 @@ class ChartArea extends React.Component { dataSet => (dataSet.data && dataSet.data.length && ( )) || null @@ -256,21 +275,22 @@ ChartArea.propTypes = { yAxisLabel: PropTypes.string }) ), + dataAnimate: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]), + dataColor: PropTypes.string, dataInterpolation: PropTypes.string, + dataLegendLabel: PropTypes.string, + dataStyle: PropTypes.object, threshold: PropTypes.arrayOf( PropTypes.shape({ x: PropTypes.number, y: PropTypes.number }) ), + thresholdAnimate: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]), + thresholdColor: PropTypes.string, thresholdInterpolation: PropTypes.string, + thresholdLegendLabel: PropTypes.string, thresholdStyle: PropTypes.object, - legendData: PropTypes.shape({ - name: PropTypes.string - }), - legendThreshold: PropTypes.shape({ - name: PropTypes.string - }), xAxisLabelUseDataSet: PropTypes.bool, yAxisLabelUseDataSet: PropTypes.bool }) diff --git a/src/components/rhelGraphCard/__tests__/__snapshots__/rhelGraphCard.test.js.snap b/src/components/rhelGraphCard/__tests__/__snapshots__/rhelGraphCard.test.js.snap index 96d26631e..e88e46244 100644 --- a/src/components/rhelGraphCard/__tests__/__snapshots__/rhelGraphCard.test.js.snap +++ b/src/components/rhelGraphCard/__tests__/__snapshots__/rhelGraphCard.test.js.snap @@ -1,231 +1,283 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`RhelGraphCard Component should render a non-connected component: non-connected 1`] = ` - - -
+

+ CPU socket usage +

+ + - - -
- -
- - -
-
-
- - - - + +
+
+ `; exports[`RhelGraphCard Component should render multiple states: error shows zeroed bar values 1`] = ` @@ -419,13 +471,22 @@ Object { "y": 0, }, ], - "legendData": Object { - "name": "t('curiosity-graph.legendSocketsLabel')", + "dataAnimate": Object { + "duration": 250, + "onLoad": Object { + "duration": 250, + }, }, - "legendThreshold": Object { - "name": "t('curiosity-graph.legendSocketsThresholdLabel')", + "dataLegendLabel": "t('curiosity-graph.legendSocketsLabel')", + "threshold": Array [], + "thresholdAnimate": Object { + "duration": 100, + "onLoad": Object { + "duration": 100, + }, }, - "thresholds": Array [], + "thresholdColor": "green", + "thresholdLegendLabel": "t('curiosity-graph.legendSocketsThresholdLabel')", }, ], } @@ -669,13 +730,22 @@ exports[`RhelGraphCard Component should render multiple states: fulfilled 1`] = "y": 0, }, ], - "legendData": Object { - "name": "t('curiosity-graph.legendSocketsLabel')", + "dataAnimate": Object { + "duration": 250, + "onLoad": Object { + "duration": 250, + }, }, - "legendThreshold": Object { - "name": "t('curiosity-graph.legendSocketsThresholdLabel')", + "dataLegendLabel": "t('curiosity-graph.legendSocketsLabel')", + "threshold": Array [], + "thresholdAnimate": Object { + "duration": 100, + "onLoad": Object { + "duration": 100, + }, }, - "thresholds": Array [], + "thresholdColor": "green", + "thresholdLegendLabel": "t('curiosity-graph.legendSocketsThresholdLabel')", }, ] } diff --git a/src/components/rhelGraphCard/__tests__/rhelGraphCard.test.js b/src/components/rhelGraphCard/__tests__/rhelGraphCard.test.js index b088a1e68..c0fb3e7a3 100644 --- a/src/components/rhelGraphCard/__tests__/rhelGraphCard.test.js +++ b/src/components/rhelGraphCard/__tests__/rhelGraphCard.test.js @@ -1,5 +1,5 @@ import React from 'react'; -import { mount, shallow } from 'enzyme'; +import { shallow } from 'enzyme'; import { ChartArea } from '../../chartArea/chartArea'; import { RhelGraphCard } from '../rhelGraphCard'; import { rhelApiTypes } from '../../../types/rhelApiTypes'; @@ -7,7 +7,7 @@ import { rhelApiTypes } from '../../../types/rhelApiTypes'; describe('RhelGraphCard Component', () => { it('should render a non-connected component', () => { const props = {}; - const component = mount(); + const component = shallow(); expect(component).toMatchSnapshot('non-connected'); }); @@ -19,22 +19,22 @@ describe('RhelGraphCard Component', () => { graphData: { usage: [ { - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_CORES]: 56, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_DATE]: '2019-06-01T00:00:00Z', - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_INSTANCES]: 28, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS]: 5 + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.CORES]: 56, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.DATE]: '2019-06-01T00:00:00Z', + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.INSTANCES]: 28, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.SOCKETS]: 5 }, { - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_CORES]: 30, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_DATE]: '2019-06-08T00:00:00Z', - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_INSTANCES]: 28, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS]: 7 + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.CORES]: 30, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.DATE]: '2019-06-08T00:00:00Z', + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.INSTANCES]: 28, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.SOCKETS]: 7 }, { - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_CORES]: 40, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_DATE]: '2019-06-25T00:00:00Z', - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_INSTANCES]: 28, - [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS]: 3 + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.CORES]: 40, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.DATE]: '2019-06-25T00:00:00Z', + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.INSTANCES]: 28, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.SOCKETS]: 3 } ] } diff --git a/src/components/rhelGraphCard/rhelGraphCard.js b/src/components/rhelGraphCard/rhelGraphCard.js index cb41fb6ea..65872d68d 100644 --- a/src/components/rhelGraphCard/rhelGraphCard.js +++ b/src/components/rhelGraphCard/rhelGraphCard.js @@ -2,9 +2,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import numeral from 'numeral'; import { Card, CardHead, CardActions, CardBody } from '@patternfly/react-core'; +import { ChartThemeColor } from '@patternfly/react-charts'; import { Skeleton, SkeletonSize } from '@redhat-cloud-services/frontend-components'; import { Select } from '../select/select'; -import { connectTranslate, reduxActions, reduxTypes, store } from '../../redux'; +import { connectTranslate, reduxActions, reduxSelectors, reduxTypes, store } from '../../redux'; import { helpers, dateHelpers, graphHelpers } from '../../common'; import { rhelApiTypes } from '../../types/rhelApiTypes'; import { rhelGraphCardTypes } from './rhelGraphCardTypes'; @@ -26,13 +27,15 @@ class RhelGraphCard extends React.Component { } onUpdateGraphData = () => { - const { getGraphReports, graphGranularity, startDate, endDate } = this.props; - - getGraphReports({ + const { getGraphCapacityRhel, getGraphReportsRhel, graphGranularity, startDate, endDate } = this.props; + const submit = { [rhelApiTypes.RHSM_API_QUERY_GRANULARITY]: graphGranularity, [rhelApiTypes.RHSM_API_QUERY_START_DATE]: startDate.toISOString(), [rhelApiTypes.RHSM_API_QUERY_END_DATE]: endDate.toISOString() - }); + }; + + getGraphCapacityRhel(submit); + getGraphReportsRhel(submit); }; onSelect = event => { @@ -52,8 +55,9 @@ class RhelGraphCard extends React.Component { const { graphData, graphGranularity, startDate, endDate, t } = this.props; const { chartXAxisLabelIncrement, chartData, chartDataThresholds } = graphHelpers.convertChartData({ data: graphData.usage, - dataFacet: rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS, - dataThresholdFacet: rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS_THRESHOLD, + dataFacet: rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.SOCKETS, + dataThreshold: graphData.capacity, + dataThresholdFacet: rhelApiTypes.RHSM_API_RESPONSE_CAPACITY_DATA_TYPES.PHYSICAL_SOCKETS, tooltipLabel: t('curiosity-graph.tooltipSockets'), tooltipThresholdLabel: t('curiosity-graph.tooltipSocketsThreshold'), startDate, @@ -69,9 +73,18 @@ class RhelGraphCard extends React.Component { dataSets={[ { data: chartData, - thresholds: chartDataThresholds, - legendThreshold: { name: t('curiosity-graph.legendSocketsThresholdLabel') }, - legendData: { name: t('curiosity-graph.legendSocketsLabel') } + dataAnimate: { + duration: 250, + onLoad: { duration: 250 } + }, + dataLegendLabel: t('curiosity-graph.legendSocketsLabel'), + threshold: chartDataThresholds, + thresholdAnimate: { + duration: 100, + onLoad: { duration: 100 } + }, + thresholdColor: ChartThemeColor.green, + thresholdLegendLabel: t('curiosity-graph.legendSocketsThresholdLabel') } ]} /> @@ -80,7 +93,7 @@ class RhelGraphCard extends React.Component { // ToDo: combine "curiosity-skeleton-container" into a single class w/ --loading and BEM style render() { - const { error, fulfilled, graphGranularity, pending, t } = this.props; + const { graphGranularity, pending, t } = this.props; const getDateMenuOptions = rhelGraphCardTypes.getDateMenuOptions(); return ( @@ -107,7 +120,7 @@ class RhelGraphCard extends React.Component { )} - {!pending && (fulfilled || error) && this.renderChart()} + {!pending && this.renderChart()}
@@ -116,10 +129,10 @@ class RhelGraphCard extends React.Component { } RhelGraphCard.propTypes = { - error: PropTypes.bool, - fulfilled: PropTypes.bool, - getGraphReports: PropTypes.func, + getGraphCapacityRhel: PropTypes.func, + getGraphReportsRhel: PropTypes.func, graphData: PropTypes.shape({ + capacity: PropTypes.array, usage: PropTypes.array }), graphGranularity: PropTypes.oneOf([ @@ -135,10 +148,10 @@ RhelGraphCard.propTypes = { }; RhelGraphCard.defaultProps = { - error: false, - fulfilled: false, - getGraphReports: helpers.noop, + getGraphCapacityRhel: helpers.noop, + getGraphReportsRhel: helpers.noop, graphData: { + capacity: [], usage: [] }, graphGranularity: GRANULARITY_TYPES.DAILY, @@ -148,14 +161,20 @@ RhelGraphCard.defaultProps = { endDate: dateHelpers.defaultDateTime.endDate }; -const mapStateToProps = state => ({ - ...state.rhelGraph -}); +const makeMapStateToProps = () => { + const getRhelGraphCard = reduxSelectors.graphCard.makeRhelGraphCard(); + + return (state, props) => ({ + ...state.rhelGraph.component, + ...getRhelGraphCard(state, props) + }); +}; const mapDispatchToProps = dispatch => ({ - getGraphReports: query => dispatch(reduxActions.rhel.getGraphReports(query)) + getGraphCapacityRhel: query => dispatch(reduxActions.rhel.getGraphCapacityRhel(query)), + getGraphReportsRhel: query => dispatch(reduxActions.rhel.getGraphReportsRhel(query)) }); -const ConnectedRhelGraphCard = connectTranslate(mapStateToProps, mapDispatchToProps)(RhelGraphCard); +const ConnectedRhelGraphCard = connectTranslate(makeMapStateToProps, mapDispatchToProps)(RhelGraphCard); export { ConnectedRhelGraphCard as default, ConnectedRhelGraphCard, RhelGraphCard }; diff --git a/src/redux/actions/__tests__/rhelActions.test.js b/src/redux/actions/__tests__/rhelActions.test.js index ccba9af8c..8134250f0 100644 --- a/src/redux/actions/__tests__/rhelActions.test.js +++ b/src/redux/actions/__tests__/rhelActions.test.js @@ -35,14 +35,26 @@ describe('RhelActions', () => { moxios.uninstall(); }); - it('Should return response content for getGraphReports method', done => { + it('Should return response content for getGraphReportsRhel method', done => { const store = generateStore(); - const dispatcher = rhelActions.getGraphReports(); + const dispatcher = rhelActions.getGraphReportsRhel(); dispatcher(store.dispatch).then(() => { const response = store.getState().rhelGraph; - expect(response.fulfilled).toBe(true); + expect(response.report.fulfilled).toBe(true); + done(); + }); + }); + + it('Should return response content for getGraphCapacity method', done => { + const store = generateStore(); + const dispatcher = rhelActions.getGraphCapacityRhel(); + + dispatcher(store.dispatch).then(() => { + const response = store.getState().rhelGraph; + + expect(response.capacity.fulfilled).toBe(true); done(); }); }); diff --git a/src/redux/actions/rhelActions.js b/src/redux/actions/rhelActions.js index 491296801..67b0d8a41 100644 --- a/src/redux/actions/rhelActions.js +++ b/src/redux/actions/rhelActions.js @@ -1,19 +1,36 @@ import { rhelTypes } from '../types'; import rhelServices from '../../services/rhelServices'; -const getGraphReports = (query = {}) => dispatch => +const getGraphReportsRhel = (query = {}) => dispatch => dispatch({ - type: rhelTypes.GET_GRAPH_REPORT, - payload: rhelServices.getGraphReportsRhsm(query), + type: rhelTypes.GET_GRAPH_REPORT_RHEL, + payload: rhelServices.getGraphReportsRhel(query), meta: { + query, notifications: { rejected: { variant: 'info', - title: 'RHSM connection has failed', - description: 'Product ID Red Hat Enterprise Linux' + title: 'Reporting connection has failed', + description: 'Product ID: Red Hat Enterprise Linux' } } } }); -export { getGraphReports as default, getGraphReports }; +const getGraphCapacityRhel = (query = {}) => dispatch => + dispatch({ + type: rhelTypes.GET_GRAPH_CAPACITY_RHEL, + payload: rhelServices.getGraphCapacityRhel(query), + meta: { + query, + notifications: { + rejected: { + variant: 'info', + title: 'Capacity connection has failed', + description: 'Product ID: Red Hat Enterprise Linux' + } + } + } + }); + +export { getGraphCapacityRhel, getGraphReportsRhel }; diff --git a/src/redux/common/__tests__/__snapshots__/reduxHelpers.test.js.snap b/src/redux/common/__tests__/__snapshots__/reduxHelpers.test.js.snap index 5a2d2d9be..ed065d45a 100644 --- a/src/redux/common/__tests__/__snapshots__/reduxHelpers.test.js.snap +++ b/src/redux/common/__tests__/__snapshots__/reduxHelpers.test.js.snap @@ -5,6 +5,7 @@ Object { "FULFILLED_ACTION": [Function], "PENDING_ACTION": [Function], "REJECTED_ACTION": [Function], + "generatedPromiseActionReducer": [Function], "getMessageFromResults": [Function], "getStatusFromResults": [Function], "setStateProp": [Function], diff --git a/src/redux/common/reduxHelpers.js b/src/redux/common/reduxHelpers.js index cc41de2be..8b3c95c41 100644 --- a/src/redux/common/reduxHelpers.js +++ b/src/redux/common/reduxHelpers.js @@ -106,10 +106,87 @@ const setStateProp = (prop, data, options) => { return obj; }; +const generatedPromiseActionReducer = (types = [], state = {}, action = {}) => { + const { type } = action; + const expandedTypes = []; + + types.forEach( + val => + (Array.isArray(val.type) && val.type.forEach(subVal => expandedTypes.push({ ref: val.ref, type: subVal }))) || + expandedTypes.push(val) + ); + + const [whichType] = expandedTypes.filter(val => + new RegExp( + `^(${REJECTED_ACTION(val.type || val)}|${PENDING_ACTION(val.type || val)}|${FULFILLED_ACTION(val.type || val)})$` + ).test(type) + ); + + if (!whichType) { + return state; + } + + const baseState = { + error: false, + errorMessage: '', + fulfilled: false, + metaData: action.meta && action.meta.data, + metaId: action.meta && action.meta.id, + metaQuery: action.meta && action.meta.query, + pending: false, + update: false + }; + + const setId = data => + (action.meta && action.meta.id && { [action.meta.id]: { ...baseState, ...data } }) || { ...baseState, ...data }; + + switch (type) { + case REJECTED_ACTION(whichType.type || whichType): + return setStateProp( + whichType.ref || null, + setId({ + error: true, + errorMessage: getMessageFromResults(action.payload), + errorStatus: getStatusFromResults(action.payload) + }), + { + state + } + ); + case PENDING_ACTION(whichType.type || whichType): + return setStateProp( + whichType.ref || null, + setId({ + pending: true + }), + { + state + } + ); + + case FULFILLED_ACTION(whichType.type || whichType): + return setStateProp( + whichType.ref || null, + setId({ + date: action.payload.headers && action.payload.headers.date, + data: (action.payload && action.payload.data) || {}, + fulfilled: true + }), + { + state + } + ); + + default: + return state; + } +}; + const reduxHelpers = { FULFILLED_ACTION, PENDING_ACTION, REJECTED_ACTION, + generatedPromiseActionReducer, getMessageFromResults, getStatusFromResults, setStateProp diff --git a/src/redux/reducers/__tests__/__snapshots__/rhelGraphReducer.test.js.snap b/src/redux/reducers/__tests__/__snapshots__/rhelGraphReducer.test.js.snap index ea24362d8..9f8ee697d 100644 --- a/src/redux/reducers/__tests__/__snapshots__/rhelGraphReducer.test.js.snap +++ b/src/redux/reducers/__tests__/__snapshots__/rhelGraphReducer.test.js.snap @@ -1,49 +1,146 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`RhelGraphReducer should handle all defined error types: rejected types GET_GRAPH_REPORT 1`] = ` +exports[`RhelGraphReducer should handle all defined error types: rejected types GET_GRAPH_CAPACITY_RHEL 1`] = ` Object { "result": Object { - "error": true, - "errorMessage": "ERROR", - "errorStatus": 0, - "fulfilled": false, - "graphData": Object { - "usage": Array [], + "capacity": Object { + "error": true, + "errorMessage": "ERROR", + "errorStatus": 0, + "fulfilled": false, + "metaData": undefined, + "metaId": undefined, + "metaQuery": undefined, + "pending": false, + "update": false, }, - "pending": false, + "component": Object {}, + "report": Object {}, }, - "type": "GET_GRAPH_REPORT_REJECTED", + "type": "GET_GRAPH_CAPACITY_RHEL_REJECTED", } `; -exports[`RhelGraphReducer should handle all defined fulfilled types: fulfilled types GET_GRAPH_REPORT 1`] = ` +exports[`RhelGraphReducer should handle all defined error types: rejected types GET_GRAPH_REPORT_RHEL 1`] = ` Object { "result": Object { - "error": false, - "errorMessage": null, - "errorStatus": null, - "fulfilled": true, - "graphData": Object { - "usage": Array [], + "capacity": Object {}, + "component": Object {}, + "report": Object { + "error": true, + "errorMessage": "ERROR", + "errorStatus": 0, + "fulfilled": false, + "metaData": undefined, + "metaId": undefined, + "metaQuery": undefined, + "pending": false, + "update": false, }, - "pending": false, }, - "type": "GET_GRAPH_REPORT_FULFILLED", + "type": "GET_GRAPH_REPORT_RHEL_REJECTED", } `; -exports[`RhelGraphReducer should handle all defined pending types: pending types GET_GRAPH_REPORT 1`] = ` +exports[`RhelGraphReducer should handle all defined fulfilled types: fulfilled types GET_GRAPH_CAPACITY_RHEL 1`] = ` Object { "result": Object { - "error": false, - "errorMessage": null, - "errorStatus": null, - "fulfilled": false, - "graphData": Object { - "usage": Array [], + "capacity": Object { + "data": Object { + "test": "success", + }, + "date": undefined, + "error": false, + "errorMessage": "", + "fulfilled": true, + "metaData": undefined, + "metaId": undefined, + "metaQuery": undefined, + "pending": false, + "update": false, }, - "pending": true, + "component": Object {}, + "report": Object {}, }, - "type": "GET_GRAPH_REPORT_PENDING", + "type": "GET_GRAPH_CAPACITY_RHEL_FULFILLED", +} +`; + +exports[`RhelGraphReducer should handle all defined fulfilled types: fulfilled types GET_GRAPH_REPORT_RHEL 1`] = ` +Object { + "result": Object { + "capacity": Object {}, + "component": Object {}, + "report": Object { + "data": Object { + "test": "success", + }, + "date": undefined, + "error": false, + "errorMessage": "", + "fulfilled": true, + "metaData": undefined, + "metaId": undefined, + "metaQuery": undefined, + "pending": false, + "update": false, + }, + }, + "type": "GET_GRAPH_REPORT_RHEL_FULFILLED", +} +`; + +exports[`RhelGraphReducer should handle all defined pending types: pending types GET_GRAPH_CAPACITY_RHEL 1`] = ` +Object { + "result": Object { + "capacity": Object { + "error": false, + "errorMessage": "", + "fulfilled": false, + "metaData": undefined, + "metaId": undefined, + "metaQuery": undefined, + "pending": true, + "update": false, + }, + "component": Object {}, + "report": Object {}, + }, + "type": "GET_GRAPH_CAPACITY_RHEL_PENDING", +} +`; + +exports[`RhelGraphReducer should handle all defined pending types: pending types GET_GRAPH_REPORT_RHEL 1`] = ` +Object { + "result": Object { + "capacity": Object {}, + "component": Object {}, + "report": Object { + "error": false, + "errorMessage": "", + "fulfilled": false, + "metaData": undefined, + "metaId": undefined, + "metaQuery": undefined, + "pending": true, + "update": false, + }, + }, + "type": "GET_GRAPH_REPORT_RHEL_PENDING", +} +`; + +exports[`RhelGraphReducer should handle specific defined types: defined type SET_GRAPH_RHEL_GRANULARITY 1`] = ` +Object { + "result": Object { + "capacity": Object {}, + "component": Object { + "endDate": 2019-07-20T00:00:00.000Z, + "graphGranularity": undefined, + "startDate": 2019-06-20T00:00:00.000Z, + }, + "report": Object {}, + }, + "type": "SET_GRAPH_RHEL_GRANULARITY", } `; diff --git a/src/redux/reducers/__tests__/rhelGraphReducer.test.js b/src/redux/reducers/__tests__/rhelGraphReducer.test.js index c6a63764b..7f6b4dacb 100644 --- a/src/redux/reducers/__tests__/rhelGraphReducer.test.js +++ b/src/redux/reducers/__tests__/rhelGraphReducer.test.js @@ -7,8 +7,22 @@ describe('RhelGraphReducer', () => { expect(rhelGraphReducer.initialState).toBeDefined(); }); + it('should handle specific defined types', () => { + const specificTypes = [types.SET_GRAPH_RHEL_GRANULARITY]; + + specificTypes.forEach(value => { + const dispatched = { + type: value + }; + + const resultState = rhelGraphReducer(undefined, dispatched); + + expect({ type: value, result: resultState }).toMatchSnapshot(`defined type ${value}`); + }); + }); + it('should handle all defined error types', () => { - const specificTypes = [types.GET_GRAPH_REPORT]; + const specificTypes = [types.GET_GRAPH_CAPACITY_RHEL, types.GET_GRAPH_REPORT_RHEL]; specificTypes.forEach(value => { const dispatched = { @@ -35,7 +49,7 @@ describe('RhelGraphReducer', () => { }); it('should handle all defined pending types', () => { - const specificTypes = [types.GET_GRAPH_REPORT]; + const specificTypes = [types.GET_GRAPH_CAPACITY_RHEL, types.GET_GRAPH_REPORT_RHEL]; specificTypes.forEach(value => { const dispatched = { @@ -51,7 +65,7 @@ describe('RhelGraphReducer', () => { }); it('should handle all defined fulfilled types', () => { - const specificTypes = [types.GET_GRAPH_REPORT]; + const specificTypes = [types.GET_GRAPH_CAPACITY_RHEL, types.GET_GRAPH_REPORT_RHEL]; specificTypes.forEach(value => { const dispatched = { diff --git a/src/redux/reducers/rhelGraphReducer.js b/src/redux/reducers/rhelGraphReducer.js index 592c7bb1d..3d1f09f22 100644 --- a/src/redux/reducers/rhelGraphReducer.js +++ b/src/redux/reducers/rhelGraphReducer.js @@ -1,65 +1,18 @@ import { reduxTypes, rhelTypes } from '../types'; -import { rhelApiTypes } from '../../types/rhelApiTypes'; import { reduxHelpers } from '../common/reduxHelpers'; import { dateHelpers } from '../../common/dateHelpers'; const initialState = { - graphData: { - usage: [] - }, - error: false, - errorStatus: null, - errorMessage: null, - pending: false, - fulfilled: false + capacity: {}, + component: {}, + report: {} }; const rhelGraphReducer = (state = initialState, action) => { switch (action.type) { - case reduxHelpers.REJECTED_ACTION(rhelTypes.GET_GRAPH_REPORT): - return reduxHelpers.setStateProp( - null, - { - error: action.error, - errorMessage: reduxHelpers.getMessageFromResults(action.payload), - errorStatus: reduxHelpers.getStatusFromResults(action.payload) - }, - { - state, - initialState - } - ); - - case reduxHelpers.PENDING_ACTION(rhelTypes.GET_GRAPH_REPORT): - return reduxHelpers.setStateProp( - null, - { - pending: true - }, - { - state, - initialState - } - ); - - case reduxHelpers.FULFILLED_ACTION(rhelTypes.GET_GRAPH_REPORT): - return reduxHelpers.setStateProp( - null, - { - graphData: { - usage: action.payload.data[rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA] || [] - }, - fulfilled: true - }, - { - state, - initialState - } - ); - case reduxTypes.rhel.SET_GRAPH_RHEL_GRANULARITY: return reduxHelpers.setStateProp( - null, + 'component', { graphGranularity: action.graphGranularity, ...dateHelpers.getRangedDateTime(action.graphGranularity) @@ -71,7 +24,14 @@ const rhelGraphReducer = (state = initialState, action) => { ); default: - return state; + return reduxHelpers.generatedPromiseActionReducer( + [ + { ref: 'capacity', type: rhelTypes.GET_GRAPH_CAPACITY_RHEL }, + { ref: 'report', type: rhelTypes.GET_GRAPH_REPORT_RHEL } + ], + state, + action + ); } }; diff --git a/src/redux/selectors/__tests__/__snapshots__/graphCardSelectors.test.js.snap b/src/redux/selectors/__tests__/__snapshots__/graphCardSelectors.test.js.snap new file mode 100644 index 000000000..7cd0ba7f5 --- /dev/null +++ b/src/redux/selectors/__tests__/__snapshots__/graphCardSelectors.test.js.snap @@ -0,0 +1,99 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`GraphCardSelectors Should error on a RHEL product ID without granularity provided: rhelGraphCard: no granularity error 1`] = ` +Object { + "cached": false, + "error": true, + "fulfilled": false, + "graphData": Object { + "capacity": Array [], + "usage": Array [], + }, + "initialLoad": true, + "pending": false, +} +`; + +exports[`GraphCardSelectors Should handle pending state on a RHEL product ID: rhelGraphCard: pending 1`] = ` +Object { + "cached": false, + "error": false, + "fulfilled": false, + "graphData": Object { + "capacity": Array [], + "usage": Array [], + }, + "initialLoad": true, + "pending": true, +} +`; + +exports[`GraphCardSelectors Should map a fulfilled RHEL product ID response to an aggregated output: rhelGraphCard: fulfilled granularity 1`] = ` +Object { + "cached": false, + "error": false, + "fulfilled": true, + "graphData": Object { + "capacity": Array [ + Object { + "date": "2019-09-04T00:00:00.000Z", + "hypervisor_sockets": 50, + "physical_sockets": 50, + }, + Object { + "date": "2019-09-05T00:00:00.000Z", + "hypervisor_sockets": 0, + "physical_sockets": 0, + }, + Object { + "date": "2019-09-06T00:00:00.000Z", + "hypervisor_sockets": 50, + "physical_sockets": 50, + }, + ], + "usage": Array [ + Object { + "cores": 32, + "date": "2019-09-04T00:00:00.000Z", + "instance_count": 1, + "sockets": 1, + }, + Object { + "cores": 32, + "date": "2019-09-05T00:00:00.000Z", + "instance_count": 3, + "sockets": 1, + }, + Object { + "cores": 25, + "date": "2019-09-06T00:00:00.000Z", + "instance_count": 1, + "sockets": 2, + }, + ], + }, + "initialLoad": false, + "pending": false, +} +`; + +exports[`GraphCardSelectors Should pass data through on a RHEL product ID when granularity provided mismatches between aggregated responses: rhelGraphCard: granularity mismatch fulfilled 1`] = ` +Object { + "cached": false, + "error": false, + "fulfilled": true, + "graphData": Object { + "capacity": Array [], + "usage": Array [], + }, + "initialLoad": false, + "pending": false, +} +`; + +exports[`GraphCardSelectors should return specific selectors: selectors 1`] = ` +Object { + "makeRhelGraphCard": [Function], + "rhelGraphCard": [Function], +} +`; diff --git a/src/redux/selectors/__tests__/graphCardSelectors.test.js b/src/redux/selectors/__tests__/graphCardSelectors.test.js new file mode 100644 index 000000000..22a62820c --- /dev/null +++ b/src/redux/selectors/__tests__/graphCardSelectors.test.js @@ -0,0 +1,135 @@ +import graphCardSelectors from '../graphCardSelectors'; +import { rhelApiTypes } from '../../../types/rhelApiTypes'; + +describe('GraphCardSelectors', () => { + it('should return specific selectors', () => { + expect(graphCardSelectors).toMatchSnapshot('selectors'); + }); + + it('Should error on a RHEL product ID without granularity provided', () => { + const state = { + rhelGraph: { + capacity: { + fulfilled: true, + metaQuery: {}, + data: { [rhelApiTypes.RHSM_API_RESPONSE_CAPACITY_DATA]: [] } + }, + report: { + fulfilled: true, + metaQuery: {}, + data: { [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA]: [] } + } + } + }; + + expect(graphCardSelectors.rhelGraphCard(state)).toMatchSnapshot('rhelGraphCard: no granularity error'); + }); + + it('Should handle pending state on a RHEL product ID', () => { + const state = { + rhelGraph: { + capacity: { + fulfilled: true, + metaQuery: { + [rhelApiTypes.RHSM_API_QUERY_GRANULARITY]: rhelApiTypes.RHSM_API_QUERY_GRANULARITY_TYPES.DAILY + }, + data: { [rhelApiTypes.RHSM_API_RESPONSE_CAPACITY_DATA]: [] } + }, + report: { + pending: true, + metaQuery: { + [rhelApiTypes.RHSM_API_QUERY_GRANULARITY]: rhelApiTypes.RHSM_API_QUERY_GRANULARITY_TYPES.DAILY + }, + data: { [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA]: [] } + } + } + }; + + expect(graphCardSelectors.rhelGraphCard(state)).toMatchSnapshot('rhelGraphCard: pending'); + }); + + it('Should pass data through on a RHEL product ID when granularity provided mismatches between aggregated responses', () => { + const state = { + rhelGraph: { + capacity: { + fulfilled: true, + metaQuery: { + [rhelApiTypes.RHSM_API_QUERY_GRANULARITY]: rhelApiTypes.RHSM_API_QUERY_GRANULARITY_TYPES.MONTHLY + }, + data: { [rhelApiTypes.RHSM_API_RESPONSE_CAPACITY_DATA]: [] } + }, + report: { + fulfilled: true, + metaQuery: { + [rhelApiTypes.RHSM_API_QUERY_GRANULARITY]: rhelApiTypes.RHSM_API_QUERY_GRANULARITY_TYPES.DAILY + }, + data: { [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA]: [] } + } + } + }; + + expect(graphCardSelectors.rhelGraphCard(state)).toMatchSnapshot('rhelGraphCard: granularity mismatch fulfilled'); + }); + + it('Should map a fulfilled RHEL product ID response to an aggregated output', () => { + const state = { + rhelGraph: { + capacity: { + fulfilled: true, + metaQuery: { + [rhelApiTypes.RHSM_API_QUERY_GRANULARITY]: rhelApiTypes.RHSM_API_QUERY_GRANULARITY_TYPES.DAILY + }, + data: { + [rhelApiTypes.RHSM_API_RESPONSE_CAPACITY_DATA]: [ + { + [rhelApiTypes.RHSM_API_RESPONSE_CAPACITY_DATA_TYPES.DATE]: '2019-09-04T00:00:00.000Z', + [rhelApiTypes.RHSM_API_RESPONSE_CAPACITY_DATA_TYPES.HYPERVISOR_SOCKETS]: 50, + [rhelApiTypes.RHSM_API_RESPONSE_CAPACITY_DATA_TYPES.PHYSICAL_SOCKETS]: 50 + }, + { + [rhelApiTypes.RHSM_API_RESPONSE_CAPACITY_DATA_TYPES.DATE]: '2019-09-05T00:00:00.000Z', + [rhelApiTypes.RHSM_API_RESPONSE_CAPACITY_DATA_TYPES.HYPERVISOR_SOCKETS]: 0, + [rhelApiTypes.RHSM_API_RESPONSE_CAPACITY_DATA_TYPES.PHYSICAL_SOCKETS]: 0 + }, + { + [rhelApiTypes.RHSM_API_RESPONSE_CAPACITY_DATA_TYPES.DATE]: '2019-09-06T00:00:00.000Z', + [rhelApiTypes.RHSM_API_RESPONSE_CAPACITY_DATA_TYPES.HYPERVISOR_SOCKETS]: 50, + [rhelApiTypes.RHSM_API_RESPONSE_CAPACITY_DATA_TYPES.PHYSICAL_SOCKETS]: 50 + } + ] + } + }, + report: { + fulfilled: true, + metaQuery: { + [rhelApiTypes.RHSM_API_QUERY_GRANULARITY]: rhelApiTypes.RHSM_API_QUERY_GRANULARITY_TYPES.DAILY + }, + data: { + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA]: [ + { + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.CORES]: 32, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.DATE]: '2019-09-04T00:00:00.000Z', + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.INSTANCES]: 1, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.SOCKETS]: 1 + }, + { + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.CORES]: 32, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.DATE]: '2019-09-05T00:00:00.000Z', + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.INSTANCES]: 3, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.SOCKETS]: 1 + }, + { + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.CORES]: 25, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.DATE]: '2019-09-06T00:00:00.000Z', + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.INSTANCES]: 1, + [rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES.SOCKETS]: 2 + } + ] + } + } + } + }; + + expect(graphCardSelectors.rhelGraphCard(state)).toMatchSnapshot('rhelGraphCard: fulfilled granularity'); + }); +}); diff --git a/src/redux/selectors/graphCardSelectors.js b/src/redux/selectors/graphCardSelectors.js new file mode 100644 index 000000000..1bfd86291 --- /dev/null +++ b/src/redux/selectors/graphCardSelectors.js @@ -0,0 +1,65 @@ +import { createSelector } from 'reselect'; +import _get from 'lodash/get'; +import { rhelApiTypes } from '../../types/rhelApiTypes'; + +const rhelGraphCardCache = {}; + +const rhelGraph = state => state.rhelGraph; + +const rhelGraphCardSelector = createSelector( + [rhelGraph], + rhelGraphReducer => { + const { capacity = {}, report = {} } = rhelGraphReducer || {}; + const reportGranularity = _get(report, ['metaQuery', rhelApiTypes.RHSM_API_QUERY_GRANULARITY]); + const capacityGranularity = _get(capacity, ['metaQuery', rhelApiTypes.RHSM_API_QUERY_GRANULARITY]); + const granularity = reportGranularity || capacityGranularity || null; + const cachedGranularity = (granularity && rhelGraphCardCache[granularity]) || {}; + const initialLoad = typeof cachedGranularity.initialLoad === 'boolean' ? cachedGranularity.initialLoad : true; + + const updatedData = { + cached: false, + error: false, + fulfilled: false, + pending: false, + initialLoad, + graphData: { + usage: [], + capacity: [] + }, + ...cachedGranularity + }; + + if (granularity === null) { + updatedData.error = true; + return updatedData; + } + + if (initialLoad) { + updatedData.error = report.error || capacity.error || false; + updatedData.pending = report.pending || capacity.pending || false; + } + + if (capacity.fulfilled && report.fulfilled && granularity) { + updatedData.graphData.usage = _get(report, ['data', rhelApiTypes.RHSM_API_RESPONSE_PRODUCTS_DATA], []); + updatedData.graphData.capacity = _get(capacity, ['data', rhelApiTypes.RHSM_API_RESPONSE_CAPACITY_DATA], []); + updatedData.initialLoad = false; + updatedData.fulfilled = true; + updatedData.cached = false; + + if (reportGranularity === capacityGranularity) { + rhelGraphCardCache[granularity] = { ...updatedData, cached: true }; + } + } + + return updatedData; + } +); + +const makeRhelGraphCardSelector = () => rhelGraphCardSelector; + +const graphCardSelectors = { + rhelGraphCard: rhelGraphCardSelector, + makeRhelGraphCard: makeRhelGraphCardSelector +}; + +export { graphCardSelectors as default, graphCardSelectors, rhelGraphCardSelector, makeRhelGraphCardSelector }; diff --git a/src/redux/selectors/index.js b/src/redux/selectors/index.js index d80924be0..f329aea00 100644 --- a/src/redux/selectors/index.js +++ b/src/redux/selectors/index.js @@ -1,3 +1,7 @@ -const reduxSelectors = {}; +import graphCardSelectors from './graphCardSelectors'; + +const reduxSelectors = { + graphCard: graphCardSelectors +}; export { reduxSelectors as default, reduxSelectors }; diff --git a/src/redux/types/__tests__/__snapshots__/index.test.js.snap b/src/redux/types/__tests__/__snapshots__/index.test.js.snap index 7a0c32005..37398b996 100644 --- a/src/redux/types/__tests__/__snapshots__/index.test.js.snap +++ b/src/redux/types/__tests__/__snapshots__/index.test.js.snap @@ -4,7 +4,8 @@ exports[`ReduxTypes should have specific type properties: all redux types 1`] = Object { "default": Object { "rhel": Object { - "GET_GRAPH_REPORT": "GET_GRAPH_REPORT", + "GET_GRAPH_CAPACITY_RHEL": "GET_GRAPH_CAPACITY_RHEL", + "GET_GRAPH_REPORT_RHEL": "GET_GRAPH_REPORT_RHEL", "SET_GRAPH_RHEL_GRANULARITY": "SET_GRAPH_RHEL_GRANULARITY", }, "user": Object { @@ -15,7 +16,8 @@ Object { }, "reduxTypes": Object { "rhel": Object { - "GET_GRAPH_REPORT": "GET_GRAPH_REPORT", + "GET_GRAPH_CAPACITY_RHEL": "GET_GRAPH_CAPACITY_RHEL", + "GET_GRAPH_REPORT_RHEL": "GET_GRAPH_REPORT_RHEL", "SET_GRAPH_RHEL_GRANULARITY": "SET_GRAPH_RHEL_GRANULARITY", }, "user": Object { @@ -25,7 +27,8 @@ Object { }, }, "rhelTypes": Object { - "GET_GRAPH_REPORT": "GET_GRAPH_REPORT", + "GET_GRAPH_CAPACITY_RHEL": "GET_GRAPH_CAPACITY_RHEL", + "GET_GRAPH_REPORT_RHEL": "GET_GRAPH_REPORT_RHEL", "SET_GRAPH_RHEL_GRANULARITY": "SET_GRAPH_RHEL_GRANULARITY", }, "userTypes": Object { @@ -39,7 +42,8 @@ Object { exports[`ReduxTypes should have specific type properties: specific types 1`] = ` Object { "rhel": Object { - "GET_GRAPH_REPORT": "GET_GRAPH_REPORT", + "GET_GRAPH_CAPACITY_RHEL": "GET_GRAPH_CAPACITY_RHEL", + "GET_GRAPH_REPORT_RHEL": "GET_GRAPH_REPORT_RHEL", "SET_GRAPH_RHEL_GRANULARITY": "SET_GRAPH_RHEL_GRANULARITY", }, "user": Object { diff --git a/src/redux/types/rhelTypes.js b/src/redux/types/rhelTypes.js index 5476b8f6a..ccadd5d64 100644 --- a/src/redux/types/rhelTypes.js +++ b/src/redux/types/rhelTypes.js @@ -1,4 +1,5 @@ -const GET_GRAPH_REPORT = 'GET_GRAPH_REPORT'; +const GET_GRAPH_CAPACITY_RHEL = 'GET_GRAPH_CAPACITY_RHEL'; +const GET_GRAPH_REPORT_RHEL = 'GET_GRAPH_REPORT_RHEL'; const SET_GRAPH_RHEL_GRANULARITY = 'SET_GRAPH_RHEL_GRANULARITY'; -export { GET_GRAPH_REPORT, SET_GRAPH_RHEL_GRANULARITY }; +export { GET_GRAPH_CAPACITY_RHEL, GET_GRAPH_REPORT_RHEL, SET_GRAPH_RHEL_GRANULARITY }; diff --git a/src/services/__tests__/rhelServices.test.js b/src/services/__tests__/rhelServices.test.js index 2b810647b..8955665e8 100644 --- a/src/services/__tests__/rhelServices.test.js +++ b/src/services/__tests__/rhelServices.test.js @@ -5,7 +5,7 @@ describe('RhelServices', () => { beforeEach(() => { moxios.install(); - moxios.stubRequest(/\/(cloudigrade|tally).*?/, { + moxios.stubRequest(/\/(tally|capacity).*?/, { status: 200, responseText: 'success', timeout: 1 @@ -17,13 +17,18 @@ describe('RhelServices', () => { }); it('should export a specific number of methods and classes', () => { - expect(Object.keys(rhelServices)).toHaveLength(1); + expect(Object.keys(rhelServices)).toHaveLength(2); }); it('should have specific methods', () => { - expect(rhelServices.getGraphReportsRhsm).toBeDefined(); + expect(rhelServices.getGraphCapacityRhel).toBeDefined(); + expect(rhelServices.getGraphReportsRhel).toBeDefined(); }); + /** + * timeout errors associated with this test sometimes stem from endpoint + * settings, see "before each" regex above + */ it('should return promises for every method', done => { const promises = Object.keys(rhelServices).map(value => rhelServices[value]()); diff --git a/src/services/__tests__/userServices.test.js b/src/services/__tests__/userServices.test.js index 2fab6cb04..4ff0711bd 100644 --- a/src/services/__tests__/userServices.test.js +++ b/src/services/__tests__/userServices.test.js @@ -1,17 +1,37 @@ +import moxios from 'moxios'; import Cookies from 'js-cookie'; import userServices from '../userServices'; describe('UserServices', () => { + beforeEach(() => { + moxios.install(); + + moxios.stubRequest(/\/(version).*?/, { + status: 200, + responseText: 'success', + timeout: 1 + }); + }); + + afterEach(() => { + moxios.uninstall(); + }); + it('should export a specific number of methods and classes', () => { - expect(Object.keys(userServices)).toHaveLength(3); + expect(Object.keys(userServices)).toHaveLength(4); }); it('should have specific methods', () => { expect(userServices.authorizeUser).toBeDefined(); + expect(userServices.getApiVersion).toBeDefined(); expect(userServices.getLocale).toBeDefined(); expect(userServices.logoutUser).toBeDefined(); }); + /** + * timeout errors associated with this test sometimes stem from endpoint + * settings, see "before each" regex above + */ it('should return promises for every method', done => { const promises = Object.keys(userServices).map(value => userServices[value]()); diff --git a/src/services/rhelServices.js b/src/services/rhelServices.js index 0b03d2323..8d6cdd1c3 100644 --- a/src/services/rhelServices.js +++ b/src/services/rhelServices.js @@ -44,85 +44,73 @@ import serviceConfig from './config'; * "date": "2019-07-20T00:00:00Z", * "instance_count": 10, * "cores": 20, - * "sockets": 20, - * "sockets_threshold": 30 + * "sockets": 20 * }, * { * "date": "2019-07-21T00:00:00Z", * "instance_count": 12, * "cores": 24, - * "sockets": 24, - * "sockets_threshold": 30 + * "sockets": 24 * }, * { * "date": "2019-07-22T00:00:00Z", * "instance_count": 14, * "cores": 28, - * "sockets": 28, - * "sockets_threshold": 0 + * "sockets": 28 * }, * { * "date": "2019-07-23T00:00:00Z", * "instance_count": 16, * "cores": 32, - * "sockets": 32, - * "sockets_threshold": 0 + * "sockets": 32 * }, * { * "date": "2019-07-24T00:00:00Z", * "instance_count": 18, * "cores": 36, - * "sockets": 36, - * "sockets_threshold": 50 + * "sockets": 36 * }, * { * "date": "2019-07-25T00:00:00Z", * "instance_count": 20, * "cores": 40, - * "sockets": 40, - * "sockets_threshold": 50 + * "sockets": 40 * }, * { * "date": "2019-07-26T00:00:00Z", * "instance_count": 22, * "cores": 44, - * "sockets": 44, - * "sockets_threshold": 50 + * "sockets": 44 * }, * { * "date": "2019-07-27T00:00:00Z", * "instance_count": 24, * "cores": 48, - * "sockets": 48, - * "sockets_threshold": 50 + * "sockets": 48 * }, * { * "date": "2019-07-28T00:00:00Z", * "instance_count": 26, * "cores": 52, - * "sockets": 52, - * "sockets_threshold": 50 + * "sockets": 52 * }, * { * "date": "2019-07-29T00:00:00Z", * "instance_count": 28, * "cores": 56, - * "sockets": 56, - * "sockets_threshold": 50 + * "sockets": 56 * }, * { * "date": "2019-07-30T00:00:00Z", * "instance_count": 30, * "cores": 60, - * "sockets": 60, - * "sockets_threshold": 70 + * "sockets": 60 * }, * { * "date": "2019-07-31T00:00:00Z", * "instance_count": 32, * "cores": 64, - * "sockets": 64, - * "sockets_threshold": 70 + * "sockets": 64 * } * ], * "links": { @@ -344,7 +332,7 @@ import serviceConfig from './config'; * ] * } */ -const getGraphReportsRhsm = (params = {}) => +const getGraphReportsRhel = (params = {}) => axios( serviceConfig({ url: process.env.REACT_APP_SERVICES_RHSM_REPORT_RHEL, @@ -352,6 +340,155 @@ const getGraphReportsRhsm = (params = {}) => }) ); -const rhelServices = { getGraphReportsRhsm }; +/** + * @api {get} /api/rhsm-subscriptions/v1/capacity/products/:product_id Get RHSM graph capacity data, i.e. thresholds + * @apiDescription Retrieve graph capacity data, such as thresholds. + * + * Reference [RHSM for capacity params and commands](https://github.com/RedHatInsights/rhsm-subscriptions/blob/master/api/rhsm-subscriptions-api-spec.yaml) + * + * @apiSuccessExample {json} Success-Response: + * HTTP/1.1 200 OK + * { + * "data": [ + * { + * "date": "2019-07-20T00:00:00Z", + * "sockets": 0, + * "physical_sockets": 32, + * "hypervisor_sockets": 0, + * "has_infinite_quantity": false + * }, + * { + * "date": "2019-07-21T00:00:00Z", + * "sockets": 0, + * "physical_sockets": 32, + * "hypervisor_sockets": 0, + * "has_infinite_quantity": false + * }, + * { + * "date": "2019-07-22T00:00:00Z", + * "sockets": 10, + * "physical_sockets": 32, + * "hypervisor_sockets": 5, + * "has_infinite_quantity": false + * }, + * { + * "date": "2019-07-23T00:00:00Z", + * "sockets": null, + * "physical_sockets": null, + * "hypervisor_sockets": null, + * "has_infinite_quantity": true + * }, + * { + * "date": "2019-07-24T00:00:00Z", + * "sockets": 10, + * "physical_sockets": 75, + * "hypervisor_sockets": 5, + * "has_infinite_quantity": true + * }, + * { + * "date": "2019-07-25T00:00:00Z", + * "sockets": 10, + * "physical_sockets": 75, + * "hypervisor_sockets": 5, + * "has_infinite_quantity": true + * }, + * { + * "date": "2019-07-26T00:00:00Z", + * "sockets": 10, + * "physical_sockets": 75, + * "hypervisor_sockets": 5, + * "has_infinite_quantity": true + * }, + * { + * "date": "2019-07-27T00:00:00Z", + * "sockets": 10, + * "physical_sockets": 75, + * "hypervisor_sockets": 5, + * "has_infinite_quantity": true + * }, + * { + * "date": "2019-07-28T00:00:00Z", + * "sockets": 10, + * "physical_sockets": 75, + * "hypervisor_sockets": 5, + * "has_infinite_quantity": true + * }, + * { + * "date": "2019-07-29T00:00:00Z", + * "sockets": 10, + * "physical_sockets": 75, + * "hypervisor_sockets": 5, + * "has_infinite_quantity": true + * }, + * { + * "date": "2019-07-30T00:00:00Z", + * "sockets": 10, + * "physical_sockets": 75, + * "hypervisor_sockets": 5, + * "has_infinite_quantity": true + * } + * ], + * "links": { + * "first": "/api/rhsm-subscriptions/v1/capacity/products/RHEL?granularity=daily&beginning=2019-07-20T00:00:00.000Z&ending=2019-08-19T23:59:59.999Z&offset=0", + * "last": "/api/rhsm-subscriptions/v1/capacity/products/RHEL?granularity=daily&beginning=2019-07-20T00:00:00.000Z&ending=2019-08-19T23:59:59.999Z&offset=0", + * "previous": null, + * "next": "/api/rhsm-subscriptions/v1/capacity/products/RHEL?granularity=daily&beginning=2019-07-20T00:00:00.000Z&ending=2019-08-19T23:59:59.999Z&offset=0" + * }, + * "meta": { + * "count": 11, + * "product": "RHEL", + * "granularity": "daily" + * } + * } + * + * @apiError {String} detail + * @apiErrorExample {json} Error-Response: + * HTTP/1.1 400 Bad Request + * { + * "errors": [ + * { + * "status": "string", + * "code": "string", + * "title": "string", + * "detail": "string" + * } + * ] + * } + * @apiError {String} detail + * @apiErrorExample {text} Error-Response: + * HTTP/1.1 404 Internal Server Error + * { + * "errors": [ + * { + * "status": "string", + * "code": "string", + * "title": "string", + * "detail": "string" + * } + * ] + * } + * @apiError {String} detail + * @apiErrorExample {text} Error-Response: + * HTTP/1.1 500 Internal Server Error + * { + * "errors": [ + * { + * "status": "string", + * "code": "string", + * "title": "string", + * "detail": "string" + * } + * ] + * } + */ +const getGraphCapacityRhel = (params = {}) => + axios( + serviceConfig({ + url: process.env.REACT_APP_SERVICES_RHSM_CAPACITY_RHEL, + params + }) + ); + +const rhelServices = { getGraphCapacityRhel, getGraphReportsRhel }; -export { rhelServices as default, rhelServices, getGraphReportsRhsm }; +export { rhelServices as default, rhelServices, getGraphCapacityRhel, getGraphReportsRhel }; diff --git a/src/services/userServices.js b/src/services/userServices.js index 2f362f95e..b170be9ad 100644 --- a/src/services/userServices.js +++ b/src/services/userServices.js @@ -1,5 +1,7 @@ import Cookies from 'js-cookie'; import LocaleCode from 'locale-code'; +import axios from 'axios'; +import serviceConfig from './config'; import { helpers } from '../common/helpers'; const authorizeUser = () => { @@ -18,6 +20,45 @@ const authorizeUser = () => { return returnMethod; }; +/** + * @api {get} /api/rhsm-subscriptions/v1/version + * @apiDescription Retrieve API version information + * + * Reference [RHSM API](https://github.com/RedHatInsights/rhsm-subscriptions/blob/master/api/rhsm-subscriptions-api-spec.yaml) + * + * @apiSuccessExample {json} Success-Response: + * HTTP/1.1 200 OK + * { + * "build": { + * "version": "0.0.0", + * "gitDescription": "lorem ipsum", + * "artifact": "dolor sit", + * "name": "lorem", + * "group": "ipsum", + * "gitHash": "0000000000000000" + * } + * + * @apiError {String} detail + * @apiErrorExample {text} Error-Response: + * HTTP/1.1 500 Internal Server Error + * { + * "errors": [ + * { + * "status": "string", + * "code": "string", + * "title": "string", + * "detail": "string" + * } + * ] + * } + */ +const getApiVersion = () => + axios( + serviceConfig({ + url: process.env.REACT_APP_SERVICES_RHSM_VERSION + }) + ); + const getLocaleFromCookie = () => { const value = (Cookies.get(process.env.REACT_APP_CONFIG_SERVICE_LOCALES_COOKIE) || '').replace('_', '-'); const key = (value && LocaleCode.getLanguageName(value)) || null; @@ -42,6 +83,6 @@ const logoutUser = () => resolve({}); }); -const userServices = { authorizeUser, getLocale, logoutUser }; +const userServices = { authorizeUser, getApiVersion, getLocale, logoutUser }; -export { userServices as default, userServices, authorizeUser, getLocale, logoutUser }; +export { userServices as default, userServices, authorizeUser, getApiVersion, getLocale, logoutUser }; diff --git a/src/types/__tests__/__snapshots__/rhelApiTypes.test.js.snap b/src/types/__tests__/__snapshots__/rhelApiTypes.test.js.snap index 4ae0fba39..036638f28 100644 --- a/src/types/__tests__/__snapshots__/rhelApiTypes.test.js.snap +++ b/src/types/__tests__/__snapshots__/rhelApiTypes.test.js.snap @@ -13,14 +13,28 @@ Object { "RHSM_API_QUERY_LIMIT": "limit", "RHSM_API_QUERY_OFFSET": "offset", "RHSM_API_QUERY_START_DATE": "beginning", + "RHSM_API_RESPONSE_CAPACITY_DATA": "data", + "RHSM_API_RESPONSE_CAPACITY_DATA_TYPES": Object { + "DATE": "date", + "HYPERVISOR_SOCKETS": "hypervisor_sockets", + "INFINITE": "has_infinite_quantity", + "PHYSICAL_SOCKETS": "physical_sockets", + }, + "RHSM_API_RESPONSE_CAPACITY_META": "meta", + "RHSM_API_RESPONSE_CAPACITY_META_TYPES": Object { + "COUNT": "count", + }, "RHSM_API_RESPONSE_PRODUCTS_DATA": "data", - "RHSM_API_RESPONSE_PRODUCTS_DATA_CORES": "cores", - "RHSM_API_RESPONSE_PRODUCTS_DATA_DATE": "date", - "RHSM_API_RESPONSE_PRODUCTS_DATA_INSTANCES": "instance_count", - "RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS": "sockets", - "RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS_THRESHOLD": "sockets_threshold", + "RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES": Object { + "CORES": "cores", + "DATE": "date", + "INSTANCES": "instance_count", + "SOCKETS": "sockets", + }, "RHSM_API_RESPONSE_PRODUCTS_META": "meta", - "RHSM_API_RESPONSE_PRODUCTS_META_COUNT": "count", + "RHSM_API_RESPONSE_PRODUCTS_META_TYPES": Object { + "COUNT": "count", + }, "default": Object { "RHSM_API_QUERY_END_DATE": "ending", "RHSM_API_QUERY_GRANULARITY": "granularity", @@ -33,14 +47,28 @@ Object { "RHSM_API_QUERY_LIMIT": "limit", "RHSM_API_QUERY_OFFSET": "offset", "RHSM_API_QUERY_START_DATE": "beginning", + "RHSM_API_RESPONSE_CAPACITY_DATA": "data", + "RHSM_API_RESPONSE_CAPACITY_DATA_TYPES": Object { + "DATE": "date", + "HYPERVISOR_SOCKETS": "hypervisor_sockets", + "INFINITE": "has_infinite_quantity", + "PHYSICAL_SOCKETS": "physical_sockets", + }, + "RHSM_API_RESPONSE_CAPACITY_META": "meta", + "RHSM_API_RESPONSE_CAPACITY_META_TYPES": Object { + "COUNT": "count", + }, "RHSM_API_RESPONSE_PRODUCTS_DATA": "data", - "RHSM_API_RESPONSE_PRODUCTS_DATA_CORES": "cores", - "RHSM_API_RESPONSE_PRODUCTS_DATA_DATE": "date", - "RHSM_API_RESPONSE_PRODUCTS_DATA_INSTANCES": "instance_count", - "RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS": "sockets", - "RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS_THRESHOLD": "sockets_threshold", + "RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES": Object { + "CORES": "cores", + "DATE": "date", + "INSTANCES": "instance_count", + "SOCKETS": "sockets", + }, "RHSM_API_RESPONSE_PRODUCTS_META": "meta", - "RHSM_API_RESPONSE_PRODUCTS_META_COUNT": "count", + "RHSM_API_RESPONSE_PRODUCTS_META_TYPES": Object { + "COUNT": "count", + }, }, "rhelApiTypes": Object { "RHSM_API_QUERY_END_DATE": "ending", @@ -54,14 +82,28 @@ Object { "RHSM_API_QUERY_LIMIT": "limit", "RHSM_API_QUERY_OFFSET": "offset", "RHSM_API_QUERY_START_DATE": "beginning", + "RHSM_API_RESPONSE_CAPACITY_DATA": "data", + "RHSM_API_RESPONSE_CAPACITY_DATA_TYPES": Object { + "DATE": "date", + "HYPERVISOR_SOCKETS": "hypervisor_sockets", + "INFINITE": "has_infinite_quantity", + "PHYSICAL_SOCKETS": "physical_sockets", + }, + "RHSM_API_RESPONSE_CAPACITY_META": "meta", + "RHSM_API_RESPONSE_CAPACITY_META_TYPES": Object { + "COUNT": "count", + }, "RHSM_API_RESPONSE_PRODUCTS_DATA": "data", - "RHSM_API_RESPONSE_PRODUCTS_DATA_CORES": "cores", - "RHSM_API_RESPONSE_PRODUCTS_DATA_DATE": "date", - "RHSM_API_RESPONSE_PRODUCTS_DATA_INSTANCES": "instance_count", - "RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS": "sockets", - "RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS_THRESHOLD": "sockets_threshold", + "RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES": Object { + "CORES": "cores", + "DATE": "date", + "INSTANCES": "instance_count", + "SOCKETS": "sockets", + }, "RHSM_API_RESPONSE_PRODUCTS_META": "meta", - "RHSM_API_RESPONSE_PRODUCTS_META_COUNT": "count", + "RHSM_API_RESPONSE_PRODUCTS_META_TYPES": Object { + "COUNT": "count", + }, }, } `; @@ -79,13 +121,27 @@ Object { "RHSM_API_QUERY_LIMIT": "limit", "RHSM_API_QUERY_OFFSET": "offset", "RHSM_API_QUERY_START_DATE": "beginning", + "RHSM_API_RESPONSE_CAPACITY_DATA": "data", + "RHSM_API_RESPONSE_CAPACITY_DATA_TYPES": Object { + "DATE": "date", + "HYPERVISOR_SOCKETS": "hypervisor_sockets", + "INFINITE": "has_infinite_quantity", + "PHYSICAL_SOCKETS": "physical_sockets", + }, + "RHSM_API_RESPONSE_CAPACITY_META": "meta", + "RHSM_API_RESPONSE_CAPACITY_META_TYPES": Object { + "COUNT": "count", + }, "RHSM_API_RESPONSE_PRODUCTS_DATA": "data", - "RHSM_API_RESPONSE_PRODUCTS_DATA_CORES": "cores", - "RHSM_API_RESPONSE_PRODUCTS_DATA_DATE": "date", - "RHSM_API_RESPONSE_PRODUCTS_DATA_INSTANCES": "instance_count", - "RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS": "sockets", - "RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS_THRESHOLD": "sockets_threshold", + "RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES": Object { + "CORES": "cores", + "DATE": "date", + "INSTANCES": "instance_count", + "SOCKETS": "sockets", + }, "RHSM_API_RESPONSE_PRODUCTS_META": "meta", - "RHSM_API_RESPONSE_PRODUCTS_META_COUNT": "count", + "RHSM_API_RESPONSE_PRODUCTS_META_TYPES": Object { + "COUNT": "count", + }, } `; diff --git a/src/types/rhelApiTypes.js b/src/types/rhelApiTypes.js index 2473e18d2..2e427cf16 100644 --- a/src/types/rhelApiTypes.js +++ b/src/types/rhelApiTypes.js @@ -1,11 +1,26 @@ +const RHSM_API_RESPONSE_CAPACITY_DATA = 'data'; +const RHSM_API_RESPONSE_CAPACITY_DATA_TYPES = { + DATE: 'date', + PHYSICAL_SOCKETS: 'physical_sockets', + HYPERVISOR_SOCKETS: 'hypervisor_sockets', + INFINITE: 'has_infinite_quantity' +}; +const RHSM_API_RESPONSE_CAPACITY_META = 'meta'; +const RHSM_API_RESPONSE_CAPACITY_META_TYPES = { + COUNT: 'count' +}; + const RHSM_API_RESPONSE_PRODUCTS_DATA = 'data'; -const RHSM_API_RESPONSE_PRODUCTS_DATA_CORES = 'cores'; -const RHSM_API_RESPONSE_PRODUCTS_DATA_DATE = 'date'; -const RHSM_API_RESPONSE_PRODUCTS_DATA_INSTANCES = 'instance_count'; -const RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS = 'sockets'; -const RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS_THRESHOLD = 'sockets_threshold'; +const RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES = { + CORES: 'cores', + DATE: 'date', + INSTANCES: 'instance_count', + SOCKETS: 'sockets' +}; const RHSM_API_RESPONSE_PRODUCTS_META = 'meta'; -const RHSM_API_RESPONSE_PRODUCTS_META_COUNT = 'count'; +const RHSM_API_RESPONSE_PRODUCTS_META_TYPES = { + COUNT: 'count' +}; const RHSM_API_QUERY_GRANULARITY = 'granularity'; const RHSM_API_QUERY_GRANULARITY_TYPES = { @@ -20,14 +35,14 @@ const RHSM_API_QUERY_START_DATE = 'beginning'; const RHSM_API_QUERY_END_DATE = 'ending'; const rhelApiTypes = { + RHSM_API_RESPONSE_CAPACITY_DATA, + RHSM_API_RESPONSE_CAPACITY_DATA_TYPES, + RHSM_API_RESPONSE_CAPACITY_META, + RHSM_API_RESPONSE_CAPACITY_META_TYPES, RHSM_API_RESPONSE_PRODUCTS_DATA, - RHSM_API_RESPONSE_PRODUCTS_DATA_CORES, - RHSM_API_RESPONSE_PRODUCTS_DATA_DATE, - RHSM_API_RESPONSE_PRODUCTS_DATA_INSTANCES, - RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS, - RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS_THRESHOLD, + RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES, RHSM_API_RESPONSE_PRODUCTS_META, - RHSM_API_RESPONSE_PRODUCTS_META_COUNT, + RHSM_API_RESPONSE_PRODUCTS_META_TYPES, RHSM_API_QUERY_GRANULARITY, RHSM_API_QUERY_GRANULARITY_TYPES, RHSM_API_QUERY_LIMIT, @@ -39,14 +54,14 @@ const rhelApiTypes = { export { rhelApiTypes as default, rhelApiTypes, + RHSM_API_RESPONSE_CAPACITY_DATA, + RHSM_API_RESPONSE_CAPACITY_DATA_TYPES, + RHSM_API_RESPONSE_CAPACITY_META, + RHSM_API_RESPONSE_CAPACITY_META_TYPES, RHSM_API_RESPONSE_PRODUCTS_DATA, - RHSM_API_RESPONSE_PRODUCTS_DATA_CORES, - RHSM_API_RESPONSE_PRODUCTS_DATA_DATE, - RHSM_API_RESPONSE_PRODUCTS_DATA_INSTANCES, - RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS, - RHSM_API_RESPONSE_PRODUCTS_DATA_SOCKETS_THRESHOLD, + RHSM_API_RESPONSE_PRODUCTS_DATA_TYPES, RHSM_API_RESPONSE_PRODUCTS_META, - RHSM_API_RESPONSE_PRODUCTS_META_COUNT, + RHSM_API_RESPONSE_PRODUCTS_META_TYPES, RHSM_API_QUERY_GRANULARITY, RHSM_API_QUERY_GRANULARITY_TYPES, RHSM_API_QUERY_LIMIT, diff --git a/tests/__snapshots__/i18n.test.js.snap b/tests/__snapshots__/i18n.test.js.snap index 106930dc7..adec2333a 100644 --- a/tests/__snapshots__/i18n.test.js.snap +++ b/tests/__snapshots__/i18n.test.js.snap @@ -5,11 +5,11 @@ exports[`i18n locale should generate a predictable pot output snapshot: pot outp msgstr \\"\\" \\"Content-Type: text/plain; charset=UTF-8\\\\n\\" -#: src/components/rhelGraphCard/rhelGraphCard.js:74 +#: src/components/rhelGraphCard/rhelGraphCard.js:80 msgid \\"curiosity-graph.legendSocketsLabel\\" msgstr \\"\\" -#: src/components/rhelGraphCard/rhelGraphCard.js:73 +#: src/components/rhelGraphCard/rhelGraphCard.js:87 msgid \\"curiosity-graph.legendSocketsThresholdLabel\\" msgstr \\"\\" @@ -29,15 +29,15 @@ msgstr \\"\\" msgid \\"curiosity-graph.tooltipPreviousLabelWeekly\\" msgstr \\"\\" -#: src/components/rhelGraphCard/rhelGraphCard.js:57 +#: src/components/rhelGraphCard/rhelGraphCard.js:61 msgid \\"curiosity-graph.tooltipSockets\\" msgstr \\"\\" -#: src/components/rhelGraphCard/rhelGraphCard.js:58 +#: src/components/rhelGraphCard/rhelGraphCard.js:62 msgid \\"curiosity-graph.tooltipSocketsThreshold\\" msgstr \\"\\" -#: src/components/rhelGraphCard/rhelGraphCard.js:89 +#: src/components/rhelGraphCard/rhelGraphCard.js:102 msgctxt \\"CPU socket usage\\" msgid \\"curiosity-graph.heading\\" msgstr \\"\\" @@ -57,8 +57,8 @@ msgctxt \\"Quarterly\\" msgid \\"curiosity-graph.dropdownQuarterly\\" msgstr \\"\\" -#: src/components/rhelGraphCard/rhelGraphCard.js:92 -#: src/components/rhelGraphCard/rhelGraphCard.js:96 +#: src/components/rhelGraphCard/rhelGraphCard.js:105 +#: src/components/rhelGraphCard/rhelGraphCard.js:109 msgctxt \\"Select date range\\" msgid \\"curiosity-graph.dropdownPlaceholder\\" msgstr \\"\\"