diff --git a/.env b/.env index b361ad35e..96ea09315 100644 --- a/.env +++ b/.env @@ -15,6 +15,7 @@ REACT_APP_UI_DISABLED_TOOLBAR=false REACT_APP_UI_DISABLED_GRAPH=false REACT_APP_UI_DISABLED_TABLE=false REACT_APP_UI_DISABLED_TABLE_HOSTS=false +REACT_APP_UI_DISABLED_TABLE_INSTANCES=false REACT_APP_UI_DISABLED_TABLE_SUBSCRIPTIONS=false REACT_APP_UI_LOGGER_ID=curiosity REACT_APP_UI_LOGGER_FILE=curiosity_debug_log_{0}.json @@ -37,5 +38,6 @@ REACT_APP_SERVICES_RHSM_TALLY=/api/rhsm-subscriptions/v1/tally/products/{0}/{1} REACT_APP_SERVICES_RHSM_CAPACITY=/api/rhsm-subscriptions/v1/capacity/products/ REACT_APP_SERVICES_RHSM_INVENTORY=/api/rhsm-subscriptions/v1/hosts/products/ REACT_APP_SERVICES_RHSM_INVENTORY_GUESTS=/api/rhsm-subscriptions/v1/hosts/{0}/guests +REACT_APP_SERVICES_RHSM_INVENTORY_INSTANCES=/api/rhsm-subscriptions/v1/instances/products/ REACT_APP_SERVICES_RHSM_INVENTORY_SUBSCRIPTIONS=/api/rhsm-subscriptions/v1/subscriptions/products/ REACT_APP_SERVICES_RHSM_OPTIN=/api/rhsm-subscriptions/v1/opt-in diff --git a/.env.development b/.env.development index ab1a5a313..43dd767c4 100644 --- a/.env.development +++ b/.env.development @@ -1,5 +1,5 @@ DEV_PORT=3000 -DEV_BRANCH=ci-beta +DEV_BRANCH=ci-stable REACT_APP_ENV=development @@ -9,5 +9,6 @@ REACT_APP_SERVICES_RHSM_TALLY=http://localhost:5000/api/rhsm-subscriptions/v1/ta REACT_APP_SERVICES_RHSM_CAPACITY=http://localhost:5000/api/rhsm-subscriptions/v1/capacity/products/ REACT_APP_SERVICES_RHSM_INVENTORY=http://localhost:5000/api/rhsm-subscriptions/v1/hosts/products/ REACT_APP_SERVICES_RHSM_INVENTORY_GUESTS=//localhost:5000/api/rhsm-subscriptions/v1/hosts/{0}/guests +REACT_APP_SERVICES_RHSM_INVENTORY_INSTANCES=//localhost:5000/api/rhsm-subscriptions/v1/instances/products/ REACT_APP_SERVICES_RHSM_INVENTORY_SUBSCRIPTIONS=//localhost:5000/api/rhsm-subscriptions/v1/subscriptions/products/ REACT_APP_SERVICES_RHSM_OPTIN=http://localhost:5000/api/rhsm-subscriptions/v1/opt-in diff --git a/src/common/__tests__/__snapshots__/helpers.test.js.snap b/src/common/__tests__/__snapshots__/helpers.test.js.snap index 336dda04d..dddbfd722 100644 --- a/src/common/__tests__/__snapshots__/helpers.test.js.snap +++ b/src/common/__tests__/__snapshots__/helpers.test.js.snap @@ -17,6 +17,7 @@ Object { "UI_DISABLED_GRAPH": false, "UI_DISABLED_TABLE": false, "UI_DISABLED_TABLE_HOSTS": false, + "UI_DISABLED_TABLE_INSTANCES": false, "UI_DISABLED_TABLE_SUBSCRIPTIONS": false, "UI_DISABLED_TOOLBAR": false, "UI_DISPLAY_CONFIG_NAME": "Subscriptions", @@ -56,6 +57,7 @@ Object { "UI_DISABLED_GRAPH": false, "UI_DISABLED_TABLE": false, "UI_DISABLED_TABLE_HOSTS": false, + "UI_DISABLED_TABLE_INSTANCES": false, "UI_DISABLED_TABLE_SUBSCRIPTIONS": false, "UI_DISABLED_TOOLBAR": false, "UI_DISPLAY_CONFIG_NAME": "Subscriptions", @@ -95,6 +97,7 @@ Object { "UI_DISABLED_GRAPH": false, "UI_DISABLED_TABLE": false, "UI_DISABLED_TABLE_HOSTS": false, + "UI_DISABLED_TABLE_INSTANCES": false, "UI_DISABLED_TABLE_SUBSCRIPTIONS": false, "UI_DISABLED_TOOLBAR": false, "UI_DISPLAY_CONFIG_NAME": "Subscriptions", diff --git a/src/common/helpers.js b/src/common/helpers.js index fedbd2dad..cae7772dd 100644 --- a/src/common/helpers.js +++ b/src/common/helpers.js @@ -151,6 +151,14 @@ const UI_DISABLED_TABLE = process.env.REACT_APP_UI_DISABLED_TABLE === 'true'; */ const UI_DISABLED_TABLE_HOSTS = process.env.REACT_APP_UI_DISABLED_TABLE_HOSTS === 'true'; +/** + * Disable the current instances inventory/table aspect of the UI. + * See dotenv config files for activation. + * + * @type {boolean} + */ +const UI_DISABLED_TABLE_INSTANCES = process.env.REACT_APP_UI_DISABLED_TABLE_INSTANCES === 'true'; + /** * Disable the current subscriptions inventory/table aspect of the UI. * See dotenv config files for activation. @@ -306,6 +314,7 @@ const helpers = { UI_DISABLED_GRAPH, UI_DISABLED_TABLE, UI_DISABLED_TABLE_HOSTS, + UI_DISABLED_TABLE_INSTANCES, UI_DISABLED_TABLE_SUBSCRIPTIONS, UI_DISABLED_TOOLBAR, UI_DISPLAY_NAME, diff --git a/src/redux/actions/__tests__/rhsmActions.test.js b/src/redux/actions/__tests__/rhsmActions.test.js index fb1195363..d906f4ed4 100644 --- a/src/redux/actions/__tests__/rhsmActions.test.js +++ b/src/redux/actions/__tests__/rhsmActions.test.js @@ -22,7 +22,7 @@ describe('RhsmActions', () => { beforeEach(() => { moxios.install(); - moxios.stubRequest(/\/(tally|capacity|hosts|subscriptions|version).*?/, { + moxios.stubRequest(/\/(tally|capacity|hosts|instances|subscriptions|version).*?/, { status: 200, responseText: 'success', timeout: 1, @@ -86,6 +86,17 @@ describe('RhsmActions', () => { }); }); + it('Should return response content for getInstancesInventory method', done => { + const store = generateStore(); + const dispatcher = rhsmActions.getInstancesInventory(); + + dispatcher(store.dispatch).then(() => { + const response = store.getState().inventory; + expect(response.instancesInventory.fulfilled).toBe(true); + done(); + }); + }); + it('Should return response content for getMessageReports method', done => { const store = generateStore(); const dispatcher = rhsmActions.getMessageReports(); diff --git a/src/redux/actions/rhsmActions.js b/src/redux/actions/rhsmActions.js index 935720e1d..0ba6fdd7b 100644 --- a/src/redux/actions/rhsmActions.js +++ b/src/redux/actions/rhsmActions.js @@ -95,6 +95,24 @@ const getHostsInventoryGuests = (id = null, query = {}) => dispatch => } }); +/** + * Get an instances response listing from RHSM subscriptions. + * + * @param {string} id + * @param {object} query + * @returns {Function} + */ +const getInstancesInventory = (id = null, query = {}) => dispatch => + dispatch({ + type: rhsmTypes.GET_INSTANCES_INVENTORY_RHSM, + payload: rhsmServices.getInstancesInventory(id, query), + meta: { + id, + query, + notifications: {} + } + }); + /** * Get a RHSM response from message reporting. * @@ -136,6 +154,7 @@ const rhsmActions = { getGraphTally, getHostsInventory, getHostsInventoryGuests, + getInstancesInventory, getMessageReports, getSubscriptionsInventory }; @@ -147,6 +166,7 @@ export { getGraphTally, getHostsInventory, getHostsInventoryGuests, + getInstancesInventory, getMessageReports, getSubscriptionsInventory }; diff --git a/src/redux/reducers/__tests__/__snapshots__/inventoryReducer.test.js.snap b/src/redux/reducers/__tests__/__snapshots__/inventoryReducer.test.js.snap index 729f30239..27c487844 100644 --- a/src/redux/reducers/__tests__/__snapshots__/inventoryReducer.test.js.snap +++ b/src/redux/reducers/__tests__/__snapshots__/inventoryReducer.test.js.snap @@ -12,6 +12,7 @@ Object { "status": 0, }, "hostsInventory": Object {}, + "instancesInventory": Object {}, "subscriptionsInventory": Object {}, "tabs": Object {}, }, @@ -31,6 +32,7 @@ Object { "pending": false, "status": 0, }, + "instancesInventory": Object {}, "subscriptionsInventory": Object {}, "tabs": Object {}, }, @@ -38,11 +40,32 @@ Object { } `; +exports[`InventoryReducer should handle all defined error types: rejected types GET_INSTANCES_INVENTORY_RHSM 1`] = ` +Object { + "result": Object { + "hostsGuests": Object {}, + "hostsInventory": Object {}, + "instancesInventory": Object { + "error": true, + "errorMessage": "MESSAGE", + "fulfilled": false, + "meta": Object {}, + "pending": false, + "status": 0, + }, + "subscriptionsInventory": Object {}, + "tabs": Object {}, + }, + "type": "GET_INSTANCES_INVENTORY_RHSM_REJECTED", +} +`; + exports[`InventoryReducer should handle all defined error types: rejected types GET_SUBSCRIPTIONS_INVENTORY_RHSM 1`] = ` Object { "result": Object { "hostsGuests": Object {}, "hostsInventory": Object {}, + "instancesInventory": Object {}, "subscriptionsInventory": Object { "error": true, "errorMessage": "MESSAGE", @@ -73,6 +96,7 @@ Object { "status": 0, }, "hostsInventory": Object {}, + "instancesInventory": Object {}, "subscriptionsInventory": Object {}, "tabs": Object {}, }, @@ -96,6 +120,7 @@ Object { "pending": false, "status": 0, }, + "instancesInventory": Object {}, "subscriptionsInventory": Object {}, "tabs": Object {}, }, @@ -103,11 +128,36 @@ Object { } `; +exports[`InventoryReducer should handle all defined fulfilled types: fulfilled types GET_INSTANCES_INVENTORY_RHSM 1`] = ` +Object { + "result": Object { + "hostsGuests": Object {}, + "hostsInventory": Object {}, + "instancesInventory": Object { + "data": Object { + "test": "success", + }, + "date": null, + "error": false, + "errorMessage": "", + "fulfilled": true, + "meta": Object {}, + "pending": false, + "status": 0, + }, + "subscriptionsInventory": Object {}, + "tabs": Object {}, + }, + "type": "GET_INSTANCES_INVENTORY_RHSM_FULFILLED", +} +`; + exports[`InventoryReducer should handle all defined fulfilled types: fulfilled types GET_SUBSCRIPTIONS_INVENTORY_RHSM 1`] = ` Object { "result": Object { "hostsGuests": Object {}, "hostsInventory": Object {}, + "instancesInventory": Object {}, "subscriptionsInventory": Object { "data": Object { "test": "success", @@ -137,6 +187,7 @@ Object { "pending": true, }, "hostsInventory": Object {}, + "instancesInventory": Object {}, "subscriptionsInventory": Object {}, "tabs": Object {}, }, @@ -155,6 +206,7 @@ Object { "meta": Object {}, "pending": true, }, + "instancesInventory": Object {}, "subscriptionsInventory": Object {}, "tabs": Object {}, }, @@ -162,11 +214,31 @@ Object { } `; +exports[`InventoryReducer should handle all defined pending types: pending types GET_INSTANCES_INVENTORY_RHSM 1`] = ` +Object { + "result": Object { + "hostsGuests": Object {}, + "hostsInventory": Object {}, + "instancesInventory": Object { + "error": false, + "errorMessage": "", + "fulfilled": false, + "meta": Object {}, + "pending": true, + }, + "subscriptionsInventory": Object {}, + "tabs": Object {}, + }, + "type": "GET_INSTANCES_INVENTORY_RHSM_PENDING", +} +`; + exports[`InventoryReducer should handle all defined pending types: pending types GET_SUBSCRIPTIONS_INVENTORY_RHSM 1`] = ` Object { "result": Object { "hostsGuests": Object {}, "hostsInventory": Object {}, + "instancesInventory": Object {}, "subscriptionsInventory": Object { "error": false, "errorMessage": "", @@ -185,6 +257,7 @@ Object { "result": Object { "hostsGuests": Object {}, "hostsInventory": Object {}, + "instancesInventory": Object {}, "subscriptionsInventory": Object {}, "tabs": Object { "lorem": 1, diff --git a/src/redux/reducers/__tests__/inventoryReducer.test.js b/src/redux/reducers/__tests__/inventoryReducer.test.js index cb94577fe..3512eee29 100644 --- a/src/redux/reducers/__tests__/inventoryReducer.test.js +++ b/src/redux/reducers/__tests__/inventoryReducer.test.js @@ -28,6 +28,7 @@ describe('InventoryReducer', () => { const specificTypes = [ types.GET_HOSTS_INVENTORY_RHSM, types.GET_HOSTS_INVENTORY_GUESTS_RHSM, + types.GET_INSTANCES_INVENTORY_RHSM, types.GET_SUBSCRIPTIONS_INVENTORY_RHSM ]; @@ -59,6 +60,7 @@ describe('InventoryReducer', () => { const specificTypes = [ types.GET_HOSTS_INVENTORY_RHSM, types.GET_HOSTS_INVENTORY_GUESTS_RHSM, + types.GET_INSTANCES_INVENTORY_RHSM, types.GET_SUBSCRIPTIONS_INVENTORY_RHSM ]; @@ -79,6 +81,7 @@ describe('InventoryReducer', () => { const specificTypes = [ types.GET_HOSTS_INVENTORY_RHSM, types.GET_HOSTS_INVENTORY_GUESTS_RHSM, + types.GET_INSTANCES_INVENTORY_RHSM, types.GET_SUBSCRIPTIONS_INVENTORY_RHSM ]; diff --git a/src/redux/reducers/graphReducer.js b/src/redux/reducers/graphReducer.js index 598c5823e..995b6cfeb 100644 --- a/src/redux/reducers/graphReducer.js +++ b/src/redux/reducers/graphReducer.js @@ -5,7 +5,7 @@ import { reduxHelpers } from '../common/reduxHelpers'; * Initial state. * * @private - * @type {{reportCapacity: {}, legend: {}, tallyCapacity: {}}} + * @type {{reportCapacity: {}, legend: {}, tally: {}}} */ const initialState = { legend: {}, diff --git a/src/redux/reducers/inventoryReducer.js b/src/redux/reducers/inventoryReducer.js index bf7854743..d993a2929 100644 --- a/src/redux/reducers/inventoryReducer.js +++ b/src/redux/reducers/inventoryReducer.js @@ -6,11 +6,12 @@ import { inventoryTypes } from '../types'; * Initial state. * * @private - * @type {{subscriptionsInventory: {}, tabs: {}, hostsInventory: {}, hostsGuests: {}}} + * @type {{subscriptionsInventory: {}, instancesInventory: {}, tabs: {}, hostsInventory: {}, hostsGuests: {}}} */ const initialState = { hostsInventory: {}, hostsGuests: {}, + instancesInventory: {}, subscriptionsInventory: {}, tabs: {} }; @@ -41,6 +42,7 @@ const inventoryReducer = (state = initialState, action) => { [ { ref: 'hostsInventory', type: rhsmTypes.GET_HOSTS_INVENTORY_RHSM }, { ref: 'hostsGuests', type: rhsmTypes.GET_HOSTS_INVENTORY_GUESTS_RHSM }, + { ref: 'instancesInventory', type: rhsmTypes.GET_INSTANCES_INVENTORY_RHSM }, { ref: 'subscriptionsInventory', type: rhsmTypes.GET_SUBSCRIPTIONS_INVENTORY_RHSM } ], state, diff --git a/src/redux/selectors/__tests__/__snapshots__/instancesListSelectors.test.js.snap b/src/redux/selectors/__tests__/__snapshots__/instancesListSelectors.test.js.snap new file mode 100644 index 000000000..8a2444038 --- /dev/null +++ b/src/redux/selectors/__tests__/__snapshots__/instancesListSelectors.test.js.snap @@ -0,0 +1,32 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`InstancesListSelectors should pass existing query data through response: existing query data 1`] = ` +Object { + "query": Object { + "a": "b", + "c": "d", + }, +} +`; + +exports[`InstancesListSelectors should pass existing state data through response: existing state data 1`] = ` +Object { + "data": Array [], + "meta": "meta field", + "query": Object {}, + "testing": "lorem ipsum", +} +`; + +exports[`InstancesListSelectors should pass minimal data on missing a reducer response: missing reducer error 1`] = ` +Object { + "query": Object {}, +} +`; + +exports[`InstancesListSelectors should return specific selectors: selectors 1`] = ` +Object { + "instancesList": [Function], + "makeInstancesList": [Function], +} +`; diff --git a/src/redux/selectors/__tests__/instancesListSelectors.test.js b/src/redux/selectors/__tests__/instancesListSelectors.test.js new file mode 100644 index 000000000..4569de2bf --- /dev/null +++ b/src/redux/selectors/__tests__/instancesListSelectors.test.js @@ -0,0 +1,53 @@ +import instancesListSelectors from '../instancesListSelectors'; + +describe('InstancesListSelectors', () => { + it('should return specific selectors', () => { + expect(instancesListSelectors).toMatchSnapshot('selectors'); + }); + + it('should pass minimal data on missing a reducer response', () => { + const state = {}; + expect(instancesListSelectors.instancesList(state)).toMatchSnapshot('missing reducer error'); + }); + + it('should pass existing state data through response', () => { + const state = { + inventory: { + instancesInventory: { + loremIpsum: { + testing: 'lorem ipsum', + data: { + data: [], + meta: 'meta field' + } + } + } + } + }; + const props = { + productId: 'loremIpsum' + }; + + expect(instancesListSelectors.instancesList(state, props)).toMatchSnapshot('existing state data'); + }); + + it('should pass existing query data through response', () => { + const state = { + inventory: { + instancesInventory: { + loremIpsum: {} + } + }, + view: { + query: { loremIpsum: { d: 'e' } }, + inventoryHostsQuery: { dolorSit: { a: 'b' }, loremIpsum: { c: 'd' } } + } + }; + const props = { + productId: 'loremIpsum', + viewId: 'dolorSit' + }; + + expect(instancesListSelectors.instancesList(state, props)).toMatchSnapshot('existing query data'); + }); +}); diff --git a/src/redux/selectors/index.js b/src/redux/selectors/index.js index 5d9bdb679..ee9479a8a 100644 --- a/src/redux/selectors/index.js +++ b/src/redux/selectors/index.js @@ -2,6 +2,7 @@ import appMessagesSelectors from './appMessagesSelectors'; import guestsListSelectors from './guestsListSelectors'; import graphCardSelectors from './graphCardSelectors'; import inventoryListSelectors from './inventoryListSelectors'; +import instancesListSelectors from './instancesListSelectors'; import subscriptionsListSelectors from './subscriptionsListSelectors'; import userSelectors from './userSelectors'; @@ -10,6 +11,7 @@ const reduxSelectors = { guestsList: guestsListSelectors, graphCard: graphCardSelectors, inventoryList: inventoryListSelectors, + instancesList: instancesListSelectors, subscriptionsList: subscriptionsListSelectors, user: userSelectors }; diff --git a/src/redux/selectors/instancesListSelectors.js b/src/redux/selectors/instancesListSelectors.js new file mode 100644 index 000000000..69148396b --- /dev/null +++ b/src/redux/selectors/instancesListSelectors.js @@ -0,0 +1,70 @@ +import { createSelector } from 'reselect'; +import { apiQueries } from '../common'; +import { selector as userSession } from './userSelectors'; + +/** + * Return a combined state, props object. + * + * @private + * @param {object} state + * @param {object} props + * @returns {object} + */ +const statePropsFilter = (state, props = {}) => ({ + ...state.inventory?.instancesInventory?.[props.productId] +}); + +/** + * Return a combined query object. + * + * @param {object} state + * @param {object} props + * @returns {object} + */ +const queryFilter = (state, props = {}) => { + const { inventoryHostsQuery: query } = apiQueries.parseRhsmQuery( + { + ...props.query, + ...state.view?.query?.[props.productId], + ...state.view?.query?.[props.viewId] + }, + { + inventoryHostsQuery: { + ...state.view?.inventoryHostsQuery?.[props.productId], + ...state.view?.inventoryHostsQuery?.[props.viewId] + } + } + ); + + return query; +}; + +/** + * Create selector, transform combined state, props into a consumable object. + * + * @type {{query: object}} + */ +const selector = createSelector([statePropsFilter, queryFilter], (response, query = {}) => ({ + ...response, + ...response?.data, + query +})); + +/** + * Expose selector instance. For scenarios where a selector is reused across component instances. + * + * @param {object} defaultProps + * @returns {{pending: boolean, fulfilled: boolean, graphData: object, error: boolean, session: object, + * status: (*|number)}} + */ +const makeSelector = defaultProps => (state, props) => ({ + ...userSession(state, props, defaultProps), + ...selector(state, props, defaultProps) +}); + +const instancesListSelectors = { + instancesList: selector, + makeInstancesList: makeSelector +}; + +export { instancesListSelectors as default, instancesListSelectors, selector, makeSelector }; diff --git a/src/redux/types/__tests__/__snapshots__/index.test.js.snap b/src/redux/types/__tests__/__snapshots__/index.test.js.snap index 3d932dda9..eff19c7d7 100644 --- a/src/redux/types/__tests__/__snapshots__/index.test.js.snap +++ b/src/redux/types/__tests__/__snapshots__/index.test.js.snap @@ -58,6 +58,7 @@ Object { "GET_GRAPH_TALLY_RHSM": "GET_GRAPH_TALLY_RHSM", "GET_HOSTS_INVENTORY_GUESTS_RHSM": "GET_HOSTS_INVENTORY_GUESTS_RHSM", "GET_HOSTS_INVENTORY_RHSM": "GET_HOSTS_INVENTORY_RHSM", + "GET_INSTANCES_INVENTORY_RHSM": "GET_INSTANCES_INVENTORY_RHSM", "GET_MESSAGE_REPORTS_RHSM": "GET_MESSAGE_REPORTS_RHSM", "GET_SUBSCRIPTIONS_INVENTORY_RHSM": "GET_SUBSCRIPTIONS_INVENTORY_RHSM", }, @@ -168,6 +169,7 @@ Object { "GET_GRAPH_TALLY_RHSM": "GET_GRAPH_TALLY_RHSM", "GET_HOSTS_INVENTORY_GUESTS_RHSM": "GET_HOSTS_INVENTORY_GUESTS_RHSM", "GET_HOSTS_INVENTORY_RHSM": "GET_HOSTS_INVENTORY_RHSM", + "GET_INSTANCES_INVENTORY_RHSM": "GET_INSTANCES_INVENTORY_RHSM", "GET_MESSAGE_REPORTS_RHSM": "GET_MESSAGE_REPORTS_RHSM", "GET_SUBSCRIPTIONS_INVENTORY_RHSM": "GET_SUBSCRIPTIONS_INVENTORY_RHSM", }, @@ -189,6 +191,7 @@ Object { "GET_GRAPH_TALLY_RHSM": "GET_GRAPH_TALLY_RHSM", "GET_HOSTS_INVENTORY_GUESTS_RHSM": "GET_HOSTS_INVENTORY_GUESTS_RHSM", "GET_HOSTS_INVENTORY_RHSM": "GET_HOSTS_INVENTORY_RHSM", + "GET_INSTANCES_INVENTORY_RHSM": "GET_INSTANCES_INVENTORY_RHSM", "GET_MESSAGE_REPORTS_RHSM": "GET_MESSAGE_REPORTS_RHSM", "GET_SUBSCRIPTIONS_INVENTORY_RHSM": "GET_SUBSCRIPTIONS_INVENTORY_RHSM", }, @@ -260,6 +263,7 @@ Object { "GET_GRAPH_TALLY_RHSM": "GET_GRAPH_TALLY_RHSM", "GET_HOSTS_INVENTORY_GUESTS_RHSM": "GET_HOSTS_INVENTORY_GUESTS_RHSM", "GET_HOSTS_INVENTORY_RHSM": "GET_HOSTS_INVENTORY_RHSM", + "GET_INSTANCES_INVENTORY_RHSM": "GET_INSTANCES_INVENTORY_RHSM", "GET_MESSAGE_REPORTS_RHSM": "GET_MESSAGE_REPORTS_RHSM", "GET_SUBSCRIPTIONS_INVENTORY_RHSM": "GET_SUBSCRIPTIONS_INVENTORY_RHSM", }, diff --git a/src/redux/types/rhsmTypes.js b/src/redux/types/rhsmTypes.js index 7b9bc035c..70032c4d1 100644 --- a/src/redux/types/rhsmTypes.js +++ b/src/redux/types/rhsmTypes.js @@ -2,6 +2,7 @@ const GET_GRAPH_REPORT_CAPACITY_RHSM = 'GET_GRAPH_REPORT_CAPACITY_RHSM'; const GET_GRAPH_TALLY_RHSM = 'GET_GRAPH_TALLY_RHSM'; const GET_HOSTS_INVENTORY_RHSM = 'GET_HOSTS_INVENTORY_RHSM'; const GET_HOSTS_INVENTORY_GUESTS_RHSM = 'GET_HOSTS_INVENTORY_GUESTS_RHSM'; +const GET_INSTANCES_INVENTORY_RHSM = 'GET_INSTANCES_INVENTORY_RHSM'; const GET_MESSAGE_REPORTS_RHSM = 'GET_MESSAGE_REPORTS_RHSM'; const GET_SUBSCRIPTIONS_INVENTORY_RHSM = 'GET_SUBSCRIPTIONS_INVENTORY_RHSM'; @@ -9,13 +10,15 @@ const GET_SUBSCRIPTIONS_INVENTORY_RHSM = 'GET_SUBSCRIPTIONS_INVENTORY_RHSM'; * RHSM API action, reducer types. * * @type {{GET_GRAPH_REPORT_CAPACITY_RHSM: string, GET_MESSAGE_REPORTS_RHSM: string, GET_HOSTS_INVENTORY_GUESTS_RHSM: string, - * GET_SUBSCRIPTIONS_INVENTORY_RHSM: string, GET_HOSTS_INVENTORY_RHSM: string, GET_GRAPH_TALLY_RHSM: string}} + * GET_SUBSCRIPTIONS_INVENTORY_RHSM: string, GET_HOSTS_INVENTORY_RHSM: string, GET_INSTANCES_INVENTORY_RHSM: string, + * GET_GRAPH_TALLY_RHSM: string}} */ const rhsmTypes = { GET_GRAPH_REPORT_CAPACITY_RHSM, GET_GRAPH_TALLY_RHSM, GET_HOSTS_INVENTORY_RHSM, GET_HOSTS_INVENTORY_GUESTS_RHSM, + GET_INSTANCES_INVENTORY_RHSM, GET_MESSAGE_REPORTS_RHSM, GET_SUBSCRIPTIONS_INVENTORY_RHSM }; @@ -27,6 +30,7 @@ export { GET_GRAPH_TALLY_RHSM, GET_HOSTS_INVENTORY_RHSM, GET_HOSTS_INVENTORY_GUESTS_RHSM, + GET_INSTANCES_INVENTORY_RHSM, GET_MESSAGE_REPORTS_RHSM, GET_SUBSCRIPTIONS_INVENTORY_RHSM }; diff --git a/src/services/rhsm/__tests__/__snapshots__/rhsmConstants.test.js.snap b/src/services/rhsm/__tests__/__snapshots__/rhsmConstants.test.js.snap index 144a4461f..248034a31 100644 --- a/src/services/rhsm/__tests__/__snapshots__/rhsmConstants.test.js.snap +++ b/src/services/rhsm/__tests__/__snapshots__/rhsmConstants.test.js.snap @@ -34,6 +34,32 @@ Object { "QUARTERLY": "Quarterly", "WEEKLY": "Weekly", }, + "RHSM_API_QUERY_INVENTORY_SORT_DIRECTION_TYPES": Object { + "ASCENDING": "asc", + "DESCENDING": "desc", + }, + "RHSM_API_QUERY_INVENTORY_SORT_TYPES": Object { + "CORES": "Cores", + "CORE_SECONDS": "Core-seconds", + "INSTANCE_HOURS": "Instance-hours", + "LAST_SEEN": "last_seen", + "NAME": "display_name", + "SOCKETS": "Sockets", + "STORAGE_GIBIBYTES": "Storage-gibibytes", + "TRANSFER_GIBIBYTES": "Transfer-gibibytes", + }, + "RHSM_API_QUERY_SET_INVENTORY_TYPES": Object { + "DIRECTION": "dir", + "DISPLAY_NAME": "display_name_contains", + "END_DATE": "ending", + "LIMIT": "limit", + "OFFSET": "offset", + "SLA": "sla", + "SORT": "sort", + "START_DATE": "beginning", + "UOM": "uom", + "USAGE": "usage", + }, "RHSM_API_QUERY_SET_TALLY_CAPACITY_TYPES": Object { "END_DATE": "ending", "GRANULARITY": "granularity", @@ -42,10 +68,16 @@ Object { "USAGE": "usage", }, "RHSM_API_QUERY_SET_TYPES": Object { + "DIRECTION": "dir", + "DISPLAY_NAME": "display_name_contains", "END_DATE": "ending", "GRANULARITY": "granularity", + "LIMIT": "limit", + "OFFSET": "offset", "SLA": "sla", + "SORT": "sort", "START_DATE": "beginning", + "UOM": "uom", "USAGE": "usage", }, "RHSM_API_QUERY_SLA_TYPES": Object { @@ -75,7 +107,23 @@ Object { "QUARTERLY": "Quarterly", "WEEKLY": "Weekly", }, + "RHSM_API_RESPONSE_INSTANCES_DATA_TYPES": Object { + "DISPLAY_NAME": "display_name", + "INVENTORY_ID": "inventory_id", + "LAST_SEEN": "last_seen", + "MEASUREMENTS": "measurements", + "SUBSCRIPTION_MANAGER_ID": "subscription_manager_id", + }, + "RHSM_API_RESPONSE_INSTANCES_META_TYPES": Object { + "COUNT": "count", + "MEASUREMENTS": "measurements", + "PRODUCT": "product", + }, "RHSM_API_RESPONSE_META": "meta", + "RHSM_API_RESPONSE_META_TYPES": Object { + "COUNT": "count", + "PRODUCT": "product", + }, "RHSM_API_RESPONSE_SLA_TYPES": Object { "NONE": "", "PREMIUM": "Premium", @@ -141,6 +189,32 @@ Object { "QUARTERLY": "Quarterly", "WEEKLY": "Weekly", }, + "RHSM_API_QUERY_INVENTORY_SORT_DIRECTION_TYPES": Object { + "ASCENDING": "asc", + "DESCENDING": "desc", + }, + "RHSM_API_QUERY_INVENTORY_SORT_TYPES": Object { + "CORES": "Cores", + "CORE_SECONDS": "Core-seconds", + "INSTANCE_HOURS": "Instance-hours", + "LAST_SEEN": "last_seen", + "NAME": "display_name", + "SOCKETS": "Sockets", + "STORAGE_GIBIBYTES": "Storage-gibibytes", + "TRANSFER_GIBIBYTES": "Transfer-gibibytes", + }, + "RHSM_API_QUERY_SET_INVENTORY_TYPES": Object { + "DIRECTION": "dir", + "DISPLAY_NAME": "display_name_contains", + "END_DATE": "ending", + "LIMIT": "limit", + "OFFSET": "offset", + "SLA": "sla", + "SORT": "sort", + "START_DATE": "beginning", + "UOM": "uom", + "USAGE": "usage", + }, "RHSM_API_QUERY_SET_TALLY_CAPACITY_TYPES": Object { "END_DATE": "ending", "GRANULARITY": "granularity", @@ -149,10 +223,16 @@ Object { "USAGE": "usage", }, "RHSM_API_QUERY_SET_TYPES": Object { + "DIRECTION": "dir", + "DISPLAY_NAME": "display_name_contains", "END_DATE": "ending", "GRANULARITY": "granularity", + "LIMIT": "limit", + "OFFSET": "offset", "SLA": "sla", + "SORT": "sort", "START_DATE": "beginning", + "UOM": "uom", "USAGE": "usage", }, "RHSM_API_QUERY_SLA_TYPES": Object { @@ -182,7 +262,23 @@ Object { "QUARTERLY": "Quarterly", "WEEKLY": "Weekly", }, + "RHSM_API_RESPONSE_INSTANCES_DATA_TYPES": Object { + "DISPLAY_NAME": "display_name", + "INVENTORY_ID": "inventory_id", + "LAST_SEEN": "last_seen", + "MEASUREMENTS": "measurements", + "SUBSCRIPTION_MANAGER_ID": "subscription_manager_id", + }, + "RHSM_API_RESPONSE_INSTANCES_META_TYPES": Object { + "COUNT": "count", + "MEASUREMENTS": "measurements", + "PRODUCT": "product", + }, "RHSM_API_RESPONSE_META": "meta", + "RHSM_API_RESPONSE_META_TYPES": Object { + "COUNT": "count", + "PRODUCT": "product", + }, "RHSM_API_RESPONSE_SLA_TYPES": Object { "NONE": "", "PREMIUM": "Premium", @@ -249,6 +345,32 @@ Object { "QUARTERLY": "Quarterly", "WEEKLY": "Weekly", }, + "RHSM_API_QUERY_INVENTORY_SORT_DIRECTION_TYPES": Object { + "ASCENDING": "asc", + "DESCENDING": "desc", + }, + "RHSM_API_QUERY_INVENTORY_SORT_TYPES": Object { + "CORES": "Cores", + "CORE_SECONDS": "Core-seconds", + "INSTANCE_HOURS": "Instance-hours", + "LAST_SEEN": "last_seen", + "NAME": "display_name", + "SOCKETS": "Sockets", + "STORAGE_GIBIBYTES": "Storage-gibibytes", + "TRANSFER_GIBIBYTES": "Transfer-gibibytes", + }, + "RHSM_API_QUERY_SET_INVENTORY_TYPES": Object { + "DIRECTION": "dir", + "DISPLAY_NAME": "display_name_contains", + "END_DATE": "ending", + "LIMIT": "limit", + "OFFSET": "offset", + "SLA": "sla", + "SORT": "sort", + "START_DATE": "beginning", + "UOM": "uom", + "USAGE": "usage", + }, "RHSM_API_QUERY_SET_TALLY_CAPACITY_TYPES": Object { "END_DATE": "ending", "GRANULARITY": "granularity", @@ -257,10 +379,16 @@ Object { "USAGE": "usage", }, "RHSM_API_QUERY_SET_TYPES": Object { + "DIRECTION": "dir", + "DISPLAY_NAME": "display_name_contains", "END_DATE": "ending", "GRANULARITY": "granularity", + "LIMIT": "limit", + "OFFSET": "offset", "SLA": "sla", + "SORT": "sort", "START_DATE": "beginning", + "UOM": "uom", "USAGE": "usage", }, "RHSM_API_QUERY_SLA_TYPES": Object { @@ -290,7 +418,23 @@ Object { "QUARTERLY": "Quarterly", "WEEKLY": "Weekly", }, + "RHSM_API_RESPONSE_INSTANCES_DATA_TYPES": Object { + "DISPLAY_NAME": "display_name", + "INVENTORY_ID": "inventory_id", + "LAST_SEEN": "last_seen", + "MEASUREMENTS": "measurements", + "SUBSCRIPTION_MANAGER_ID": "subscription_manager_id", + }, + "RHSM_API_RESPONSE_INSTANCES_META_TYPES": Object { + "COUNT": "count", + "MEASUREMENTS": "measurements", + "PRODUCT": "product", + }, "RHSM_API_RESPONSE_META": "meta", + "RHSM_API_RESPONSE_META_TYPES": Object { + "COUNT": "count", + "PRODUCT": "product", + }, "RHSM_API_RESPONSE_SLA_TYPES": Object { "NONE": "", "PREMIUM": "Premium", @@ -361,6 +505,32 @@ Object { "QUARTERLY": "Quarterly", "WEEKLY": "Weekly", }, + "RHSM_API_QUERY_INVENTORY_SORT_DIRECTION_TYPES": Object { + "ASCENDING": "asc", + "DESCENDING": "desc", + }, + "RHSM_API_QUERY_INVENTORY_SORT_TYPES": Object { + "CORES": "Cores", + "CORE_SECONDS": "Core-seconds", + "INSTANCE_HOURS": "Instance-hours", + "LAST_SEEN": "last_seen", + "NAME": "display_name", + "SOCKETS": "Sockets", + "STORAGE_GIBIBYTES": "Storage-gibibytes", + "TRANSFER_GIBIBYTES": "Transfer-gibibytes", + }, + "RHSM_API_QUERY_SET_INVENTORY_TYPES": Object { + "DIRECTION": "dir", + "DISPLAY_NAME": "display_name_contains", + "END_DATE": "ending", + "LIMIT": "limit", + "OFFSET": "offset", + "SLA": "sla", + "SORT": "sort", + "START_DATE": "beginning", + "UOM": "uom", + "USAGE": "usage", + }, "RHSM_API_QUERY_SET_TALLY_CAPACITY_TYPES": Object { "END_DATE": "ending", "GRANULARITY": "granularity", @@ -369,10 +539,16 @@ Object { "USAGE": "usage", }, "RHSM_API_QUERY_SET_TYPES": Object { + "DIRECTION": "dir", + "DISPLAY_NAME": "display_name_contains", "END_DATE": "ending", "GRANULARITY": "granularity", + "LIMIT": "limit", + "OFFSET": "offset", "SLA": "sla", + "SORT": "sort", "START_DATE": "beginning", + "UOM": "uom", "USAGE": "usage", }, "RHSM_API_QUERY_SLA_TYPES": Object { @@ -402,7 +578,23 @@ Object { "QUARTERLY": "Quarterly", "WEEKLY": "Weekly", }, + "RHSM_API_RESPONSE_INSTANCES_DATA_TYPES": Object { + "DISPLAY_NAME": "display_name", + "INVENTORY_ID": "inventory_id", + "LAST_SEEN": "last_seen", + "MEASUREMENTS": "measurements", + "SUBSCRIPTION_MANAGER_ID": "subscription_manager_id", + }, + "RHSM_API_RESPONSE_INSTANCES_META_TYPES": Object { + "COUNT": "count", + "MEASUREMENTS": "measurements", + "PRODUCT": "product", + }, "RHSM_API_RESPONSE_META": "meta", + "RHSM_API_RESPONSE_META_TYPES": Object { + "COUNT": "count", + "PRODUCT": "product", + }, "RHSM_API_RESPONSE_SLA_TYPES": Object { "NONE": "", "PREMIUM": "Premium", diff --git a/src/services/rhsm/__tests__/__snapshots__/rhsmSchemas.test.js.snap b/src/services/rhsm/__tests__/__snapshots__/rhsmSchemas.test.js.snap index 8daa48de4..d65fdc6e1 100644 --- a/src/services/rhsm/__tests__/__snapshots__/rhsmSchemas.test.js.snap +++ b/src/services/rhsm/__tests__/__snapshots__/rhsmSchemas.test.js.snap @@ -3,6 +3,7 @@ exports[`RHSM Schemas should have specific RHSM response schemas: specific schemas 1`] = ` Object { "errors": [Function], + "instances": [Function], "tally": [Function], } `; diff --git a/src/services/rhsm/__tests__/__snapshots__/rhsmTransformers.test.js.snap b/src/services/rhsm/__tests__/__snapshots__/rhsmTransformers.test.js.snap index 9c583b844..c483067f0 100644 --- a/src/services/rhsm/__tests__/__snapshots__/rhsmTransformers.test.js.snap +++ b/src/services/rhsm/__tests__/__snapshots__/rhsmTransformers.test.js.snap @@ -14,8 +14,25 @@ Object { } `; +exports[`RHSM Transformers should attempt to parse an instances response: instances 1`] = ` +Object { + "data": Array [ + Object { + "a": 0.0003456, + "b": 2, + "c": 1000, + }, + ], + "meta": Object { + "count": undefined, + "productId": undefined, + }, +} +`; + exports[`RHSM Transformers should have specific RHSM response transformers: specific transformers 1`] = ` Object { + "instances": [Function], "tally": [Function], } `; diff --git a/src/services/rhsm/__tests__/rhsmServices.test.js b/src/services/rhsm/__tests__/rhsmServices.test.js index b14c32d4f..490dabaaa 100644 --- a/src/services/rhsm/__tests__/rhsmServices.test.js +++ b/src/services/rhsm/__tests__/rhsmServices.test.js @@ -5,7 +5,7 @@ describe('RhsmServices', () => { beforeEach(() => { moxios.install(); - moxios.stubRequest(/\/(tally|capacity|hosts|subscriptions|version).*?/, { + moxios.stubRequest(/\/(tally|capacity|hosts|instances|subscriptions|version).*?/, { status: 200, responseText: 'success', timeout: 1 @@ -17,7 +17,7 @@ describe('RhsmServices', () => { }); it('should export a specific number of methods and classes', () => { - expect(Object.keys(rhsmServices)).toHaveLength(7); + expect(Object.keys(rhsmServices)).toHaveLength(8); }); it('should have specific methods', () => { @@ -27,6 +27,7 @@ describe('RhsmServices', () => { expect(rhsmServices.getGraphTally).toBeDefined(); expect(rhsmServices.getHostsInventory).toBeDefined(); expect(rhsmServices.getHostsInventoryGuests).toBeDefined(); + expect(rhsmServices.getInstancesInventory).toBeDefined(); expect(rhsmServices.getSubscriptionsInventory).toBeDefined(); }); diff --git a/src/services/rhsm/__tests__/rhsmTransformers.test.js b/src/services/rhsm/__tests__/rhsmTransformers.test.js index 8bcc13d1e..47e4c0c3e 100644 --- a/src/services/rhsm/__tests__/rhsmTransformers.test.js +++ b/src/services/rhsm/__tests__/rhsmTransformers.test.js @@ -1,10 +1,26 @@ import { rhsmTranformers } from '../rhsmTranformers'; +import { rhsmConstants } from '../rhsmConstants'; describe('RHSM Transformers', () => { it('should have specific RHSM response transformers', () => { expect(rhsmTranformers).toMatchSnapshot('specific transformers'); }); + it('should attempt to parse an instances response', () => { + expect( + rhsmTranformers.instances({ + [rhsmConstants.RHSM_API_RESPONSE_DATA]: [ + { + [rhsmConstants.RHSM_API_RESPONSE_INSTANCES_DATA_TYPES.MEASUREMENTS]: [1000, 0.0003456, 2] + } + ], + [rhsmConstants.RHSM_API_RESPONSE_META]: { + [rhsmConstants.RHSM_API_RESPONSE_INSTANCES_META_TYPES.MEASUREMENTS]: ['c', 'a', 'b'] + } + }) + ).toMatchSnapshot('instances'); + }); + it('should attempt to parse a tally response', () => { expect(rhsmTranformers.tally()).toMatchSnapshot('tally'); }); diff --git a/src/services/rhsm/rhsmConstants.js b/src/services/rhsm/rhsmConstants.js index 9a26a166b..7e95c7236 100644 --- a/src/services/rhsm/rhsmConstants.js +++ b/src/services/rhsm/rhsmConstants.js @@ -59,6 +59,40 @@ const RHSM_API_RESPONSE_DATA = 'data'; */ const RHSM_API_RESPONSE_META = 'meta'; +/** + * RHSM response general meta types. + * + * @type {{PRODUCT: string, COUNT: string}} + */ +const RHSM_API_RESPONSE_META_TYPES = { + COUNT: 'count', + PRODUCT: 'product' +}; + +/** + * RHSM response Instance DATA types. + * + * @type {{MEASUREMENTS: string, SUBSCRIPTION_MANAGER_ID: string, INVENTORY_ID: string, DISPLAY_NAME: string, + * LAST_SEEN: string}} + */ +const RHSM_API_RESPONSE_INSTANCES_DATA_TYPES = { + DISPLAY_NAME: 'display_name', + INVENTORY_ID: 'inventory_id', + LAST_SEEN: 'last_seen', + MEASUREMENTS: 'measurements', + SUBSCRIPTION_MANAGER_ID: 'subscription_manager_id' +}; + +/** + * RHSM response Instances META types. + * + * @type {{MEASUREMENTS: string, PRODUCT: string, COUNT: string}} + */ +const RHSM_API_RESPONSE_INSTANCES_META_TYPES = { + ...RHSM_API_RESPONSE_META_TYPES, + MEASUREMENTS: 'measurements' +}; + /** * RHSM response Tally DATA types. * @@ -73,18 +107,17 @@ const RHSM_API_RESPONSE_TALLY_DATA_TYPES = { /** * RHSM response Tally META types. * - * @type {{TOTAL_MONTHLY: string, DATE: string, HAS_CLOUDIGRADE_DATA: string, PRODUCT: string, + * @type {{TOTAL_MONTHLY: string, DATE: string, PRODUCT: string, HAS_CLOUDIGRADE_DATA: string, * HAS_CLOUDIGRADE_MISMATCH: string, HAS_DATA: string, METRIC_ID: string, COUNT: string, VALUE: string}} */ const RHSM_API_RESPONSE_TALLY_META_TYPES = { - COUNT: 'count', + ...RHSM_API_RESPONSE_META_TYPES, DATE: 'date', VALUE: 'value', HAS_CLOUDIGRADE_DATA: 'has_cloudigrade_data', HAS_CLOUDIGRADE_MISMATCH: 'has_cloudigrade_mismatch', HAS_DATA: 'has_data', METRIC_ID: 'metric_id', - PRODUCT: 'product', TOTAL_MONTHLY: 'total_monthly' }; @@ -146,12 +179,59 @@ const RHSM_API_RESPONSE_USAGE_TYPES = { const RHSM_API_QUERY_GRANULARITY_TYPES = RHSM_API_RESPONSE_GRANULARITY_TYPES; +/** + * ToDo: Clean up sort params once hosts is fully deprecated + * These sort params are focused on the instances api NOT hosts. Since there + * are minor differences, hosts sort params are maintained in /types/rhsmApiTypes.js as + * we migrate towards hosts deprecation. Subscriptions sorts + */ +/** + * RHSM API query/search parameter SORT type values for HOSTS. + * + * @type {{CORES: string, CORE_HOURS: string, HARDWARE: string, SOCKETS: string, MEASUREMENT: string, + * LAST_SEEN: string, NAME: string}} + */ +const RHSM_API_QUERY_INVENTORY_SORT_TYPES = { + ...RHSM_API_PATH_METRIC_TYPES, + LAST_SEEN: 'last_seen', + NAME: 'display_name' +}; + +/** + * RHSM API query/search parameter SORT DIRECTION type values. + * + * @type {{ASCENDING: string, DESCENDING: string}} + */ +const RHSM_API_QUERY_INVENTORY_SORT_DIRECTION_TYPES = { + ASCENDING: 'asc', + DESCENDING: 'desc' +}; + const RHSM_API_QUERY_SLA_TYPES = RHSM_API_RESPONSE_SLA_TYPES; const RHSM_API_QUERY_UOM_TYPES = RHSM_API_RESPONSE_UOM_TYPES; const RHSM_API_QUERY_USAGE_TYPES = RHSM_API_RESPONSE_USAGE_TYPES; +/** + * RHSM API query/search parameter INVENTORY type values. + * + * @type {{UOM: string, USAGE: string, DIRECTION: string, SORT: string, END_DATE: string, OFFSET: string, + * SLA: string, LIMIT: string, START_DATE: string, DISPLAY_NAME: string}} + */ +const RHSM_API_QUERY_SET_INVENTORY_TYPES = { + DIRECTION: 'dir', + DISPLAY_NAME: 'display_name_contains', + END_DATE: 'ending', + LIMIT: 'limit', + OFFSET: 'offset', + SLA: 'sla', + SORT: 'sort', + START_DATE: 'beginning', + UOM: 'uom', + USAGE: 'usage' +}; + /** * RHSM query parameter options for TALLY, CAPACITY endpoints. * @@ -168,9 +248,11 @@ const RHSM_API_QUERY_SET_TALLY_CAPACITY_TYPES = { /** * Aggregate all query set types. * - * @type {{GRANULARITY: string, USAGE: string, END_DATE: string, SLA: string, START_DATE: string}} + * @type {{UOM: string, GRANULARITY: string, USAGE: string, DIRECTION: string, SORT: string, END_DATE: string, + * OFFSET: string, SLA: string, LIMIT: string, START_DATE: string, DISPLAY_NAME: string}} */ const RHSM_API_QUERY_SET_TYPES = { + ...RHSM_API_QUERY_SET_INVENTORY_TYPES, ...RHSM_API_QUERY_SET_TALLY_CAPACITY_TYPES }; @@ -202,6 +284,9 @@ const rhsmConstants = { RHSM_API_PATH_METRIC_TYPES, RHSM_API_RESPONSE_DATA, RHSM_API_RESPONSE_META, + RHSM_API_RESPONSE_META_TYPES, + RHSM_API_RESPONSE_INSTANCES_DATA_TYPES, + RHSM_API_RESPONSE_INSTANCES_META_TYPES, RHSM_API_RESPONSE_TALLY_DATA_TYPES, RHSM_API_RESPONSE_TALLY_META_TYPES, RHSM_API_RESPONSE_ERROR_CODE_TYPES, @@ -210,9 +295,12 @@ const rhsmConstants = { RHSM_API_RESPONSE_UOM_TYPES, RHSM_API_RESPONSE_USAGE_TYPES, RHSM_API_QUERY_GRANULARITY_TYPES, + RHSM_API_QUERY_INVENTORY_SORT_TYPES, + RHSM_API_QUERY_INVENTORY_SORT_DIRECTION_TYPES, RHSM_API_QUERY_SLA_TYPES, RHSM_API_QUERY_UOM_TYPES, RHSM_API_QUERY_USAGE_TYPES, + RHSM_API_QUERY_SET_INVENTORY_TYPES, RHSM_API_QUERY_SET_TALLY_CAPACITY_TYPES, RHSM_API_QUERY_SET_TYPES }; @@ -224,6 +312,9 @@ export { RHSM_API_PATH_METRIC_TYPES, RHSM_API_RESPONSE_DATA, RHSM_API_RESPONSE_META, + RHSM_API_RESPONSE_META_TYPES, + RHSM_API_RESPONSE_INSTANCES_DATA_TYPES, + RHSM_API_RESPONSE_INSTANCES_META_TYPES, RHSM_API_RESPONSE_TALLY_DATA_TYPES, RHSM_API_RESPONSE_TALLY_META_TYPES, RHSM_API_RESPONSE_ERROR_CODE_TYPES, @@ -232,9 +323,12 @@ export { RHSM_API_RESPONSE_UOM_TYPES, RHSM_API_RESPONSE_USAGE_TYPES, RHSM_API_QUERY_GRANULARITY_TYPES, + RHSM_API_QUERY_INVENTORY_SORT_TYPES, + RHSM_API_QUERY_INVENTORY_SORT_DIRECTION_TYPES, RHSM_API_QUERY_SLA_TYPES, RHSM_API_QUERY_UOM_TYPES, RHSM_API_QUERY_USAGE_TYPES, + RHSM_API_QUERY_SET_INVENTORY_TYPES, RHSM_API_QUERY_SET_TALLY_CAPACITY_TYPES, RHSM_API_QUERY_SET_TYPES }; diff --git a/src/services/rhsm/rhsmSchemas.js b/src/services/rhsm/rhsmSchemas.js index 15a11e43e..6f1350194 100644 --- a/src/services/rhsm/rhsmSchemas.js +++ b/src/services/rhsm/rhsmSchemas.js @@ -28,6 +28,57 @@ const errorResponseSchema = Joi.object() const linksSchema = Joi.object(); +/** + * RHSM base response meta field. + * + * @type {*} Joi schema + */ +const metaResponseSchema = Joi.object() + .keys({ + count: Joi.number().integer().default(0), + product: Joi.string().valid(...Object.values(rhsmConstants.RHSM_API_PATH_PRODUCT_TYPES)) + }) + .unknown(true); + +/** + * Instances response meta field. + * + * @type {*} Joi schema + */ +const instancesMetaSchema = metaResponseSchema + .keys({ + measurements: Joi.array() + .items(Joi.string().valid(...Object.values(rhsmConstants.RHSM_API_PATH_METRIC_TYPES))) + .default([]) + }) + .unknown(true); + +/** + * Instances response item. + * + * @type {*} Joi schema + */ +const instancesItem = Joi.object({ + inventory_id: Joi.string().optional().allow(null), + display_name: Joi.string().optional().allow(null), + measurements: Joi.array().default([]), + subscription_manager_id: Joi.string().optional().allow(null), + last_seen: Joi.date().allow(null) +}) + .unknown(true) + .default(); + +/** + * Instances response. + * + * @type {*} Joi schema + */ +const instancesResponseSchema = Joi.object().keys({ + data: Joi.array().items(instancesItem).default([]), + links: linksSchema.default({}), + meta: instancesMetaSchema.default({}) +}); + /** * Tally response item. * @@ -46,13 +97,11 @@ const tallyItem = Joi.object({ * * @type {*} Joi schema */ -const tallyMetaSchema = Joi.object() +const tallyMetaSchema = metaResponseSchema .keys({ - count: Joi.number().integer().default(0), has_cloudigrade_data: Joi.boolean().optional().allow(null), has_cloudigrade_mismatch: Joi.boolean().optional().allow(null), metric_id: Joi.string().valid(...Object.values(rhsmConstants.RHSM_API_PATH_METRIC_TYPES)), - product: Joi.string().valid(...Object.values(rhsmConstants.RHSM_API_PATH_PRODUCT_TYPES)), total_monthly: tallyItem }) .unknown(true); @@ -70,6 +119,7 @@ const tallyResponseSchema = Joi.object().keys({ const rhsmSchemas = { errors: response => schemaResponse({ response, schema: errorResponseSchema, id: 'RHSM errors' }), + instances: response => schemaResponse({ response, schema: instancesResponseSchema, id: 'RHSM instances' }), tally: response => schemaResponse({ response, schema: tallyResponseSchema, id: 'RHSM tally' }) }; diff --git a/src/services/rhsm/rhsmServices.js b/src/services/rhsm/rhsmServices.js index c89ed5c53..c0ddfe069 100644 --- a/src/services/rhsm/rhsmServices.js +++ b/src/services/rhsm/rhsmServices.js @@ -2157,6 +2157,109 @@ const getHostsInventoryGuests = (id, params = {}, options = {}) => { }); }; +/** + * @apiMock {DelayResponse} 750 + * @api {get} /api/rhsm-subscriptions/v1/instances/products/:product_id Get RHSM instances table/inventory data + * @apiDescription Retrieve instances table/inventory data. + * + * Reference [RHSM for instances table/inventory](https://github.com/RedHatInsights/rhsm-subscriptions/blob/main/api/rhsm-subscriptions-api-spec.yaml) + * + * @apiSuccessExample {json} Success-Response: + * HTTP/1.1 200 OK + * { + * "data" : [ + * { + * "inventory_id": "d6214a0b-b344-4778-831c-d53dcacb2da3", + * "subscription_manager_id": "adafd9d5-5b00-42fa-a6c9-75801d45cc6d", + * "display_name": "rhv.example.com", + * "measurements": [ + * 42, + * 0.000003563, + * 1 + * ], + * "last_seen": "2020-04-01T00:00:00Z" + * }, + * { + * "inventory_id": "XXXXXX-b344-4778-831c-XXXXXXXX", + * "subscription_manager_id": "XXXXXX-5b00-42fa-XXXX-75801d45cc6d", + * "display_name": "dolor.example.com", + * "measurements": [ + * 20, + * null, + * 1000 + * ], + * "last_seen": "2020-04-02T00:00:00Z" + * }, + * { + * "inventory_id": "BBBBB-b344-4778-831c-BBBBBBB", + * "subscription_manager_id": "BBBBB-5b00-42fa-BBBBB-75801d45cc6d", + * "display_name": "lorem.example.com", + * "measurements": [ + * 4000, + * 10000.0000345678, + * 3000 + * ], + * "last_seen": "2020-04-03T00:00:00Z" + * } + * ], + * "links": {}, + * "meta": { + * "count": 3, + * "measurements": [ + * "Instance-hours", + * "Storage-gibibytes", + * "Transfer-gibibytes" + * ], + * "product": "RHEL", + * "service_level": "Premium", + * "usage": "Production" + * } + * } + * + * @apiError {Array} errors + * @apiErrorExample {json} Error-Response: + * HTTP/1.1 500 Internal Server Error + * { + * "errors": [ + * { + * "status": "string", + * "code": "string", + * "title": "string", + * "detail": "string" + * } + * ] + * } + */ +/** + * Get RHSM API instances data. + * + * @param {string} id Product ID + * @param {object} params Query/search params + * @param {object} options + * @param {boolean} options.cancel + * @param {string} options.cancelId + * @returns {Promise<*>} + */ +const getInstancesInventory = (id, params = {}, options = {}) => { + const { + cache = true, + cancel = true, + cancelId, + schema = [rhsmSchemas.instances, rhsmSchemas.errors], + transform = [rhsmTranformers.instances] + } = options; + + return serviceCall({ + url: `${process.env.REACT_APP_SERVICES_RHSM_INVENTORY_INSTANCES}${id}`, + params, + cache, + cancel, + cancelId, + schema, + transform + }); +}; + /** * @apiMock {DelayResponse} 250 * @api {get} /api/rhsm-subscriptions/v1/subscriptions/products/:product_id Get RHSM subscriptions table/inventory data @@ -2265,6 +2368,7 @@ const rhsmServices = { getGraphTally, getHostsInventory, getHostsInventoryGuests, + getInstancesInventory, getSubscriptionsInventory }; @@ -2282,5 +2386,6 @@ export { getGraphTally, getHostsInventory, getHostsInventoryGuests, + getInstancesInventory, getSubscriptionsInventory }; diff --git a/src/services/rhsm/rhsmTranformers.js b/src/services/rhsm/rhsmTranformers.js index d2293219d..e7ce5672a 100644 --- a/src/services/rhsm/rhsmTranformers.js +++ b/src/services/rhsm/rhsmTranformers.js @@ -1,14 +1,63 @@ import { + RHSM_API_RESPONSE_INSTANCES_DATA_TYPES as INSTANCES_DATA_TYPES, + RHSM_API_RESPONSE_INSTANCES_META_TYPES as INSTANCES_META_TYPES, RHSM_API_RESPONSE_TALLY_DATA_TYPES as TALLY_DATA_TYPES, RHSM_API_RESPONSE_TALLY_META_TYPES as TALLY_META_TYPES, rhsmConstants } from './rhsmConstants'; import { dateHelpers } from '../../common'; +/** + * FixMe: If RHSM Instances is deprecating Hosts we're missing a property, number_of_guests + */ +/** + * Parse RHSM instances response for caching. + * + * @param {object} response + * @returns {object} + */ +const rhsmInstances = response => { + const updatedResponse = {}; + const { [rhsmConstants.RHSM_API_RESPONSE_DATA]: data = [], [rhsmConstants.RHSM_API_RESPONSE_META]: meta = {} } = + response || {}; + const metaMeasurements = meta[INSTANCES_META_TYPES.MEASUREMENTS]; + + updatedResponse.data = data.map(({ [INSTANCES_DATA_TYPES.MEASUREMENTS]: measurements, ...dataResponse }) => { + const updatedData = { + ...dataResponse + }; + + metaMeasurements?.forEach((measurement, index) => { + updatedData[measurement] = measurements[index]; + }); + + return updatedData; + }); + + updatedResponse.meta = { + count: meta[INSTANCES_META_TYPES.COUNT], + productId: meta[INSTANCES_META_TYPES.PRODUCT] + }; + + return updatedResponse; +}; + +/** + * Parse RHSM tally response for caching. + * + * @param {object} response + * @returns {object} + */ const rhsmTally = response => { const updatedResponse = {}; const { [rhsmConstants.RHSM_API_RESPONSE_DATA]: data = [], [rhsmConstants.RHSM_API_RESPONSE_META]: meta = {} } = response || {}; + /** + * FixMe: this is a potential bug + * Under RHOSAK this can't be seen because we're using a monthly range. However, under something like RHEL + * where a "quarterly range" that spans a year+ days of the month repeat. We need to match the full date + * instead of just the day. + */ const currentDay = dateHelpers.getCurrentDate()?.getDate(); updatedResponse.data = data.map( @@ -37,7 +86,8 @@ const rhsmTally = response => { }; const rhsmTranformers = { + instances: rhsmInstances, tally: rhsmTally }; -export { rhsmTranformers as default, rhsmTranformers, rhsmTally }; +export { rhsmTranformers as default, rhsmTranformers, rhsmInstances, rhsmTally }; diff --git a/src/types/rhsmApiTypes.js b/src/types/rhsmApiTypes.js index a839203bd..d92771b58 100644 --- a/src/types/rhsmApiTypes.js +++ b/src/types/rhsmApiTypes.js @@ -222,6 +222,11 @@ const RHSM_API_QUERY_GRANULARITY_TYPES = { ...rhsmConstants.RHSM_API_QUERY_GRANULARITY_TYPES }; +/** + * ToDo: Clean up sort params since they're hosts API specific. + * Hosts is being deprecated in favor of instances API. Instances sort params + * can be found in /services/rhsm/rhsmConstants.js + */ /** * RHSM API query/search parameter SORT type values for HOSTS. * @@ -270,8 +275,7 @@ const RHSM_API_QUERY_SUBSCRIPTIONS_SORT_TYPES = { * @type {{ASCENDING: string, DESCENDING: string}} */ const RHSM_API_QUERY_SORT_DIRECTION_TYPES = { - ASCENDING: 'asc', - DESCENDING: 'desc' + ...rhsmConstants.RHSM_API_QUERY_INVENTORY_SORT_DIRECTION_TYPES }; /** @@ -328,16 +332,7 @@ const RHSM_API_QUERY_SET_REPORT_CAPACITY_TYPES = { * SLA: string, LIMIT: string}} */ const RHSM_API_QUERY_SET_INVENTORY_TYPES = { - DIRECTION: 'dir', - DISPLAY_NAME: 'display_name_contains', - END_DATE: 'ending', - LIMIT: 'limit', - OFFSET: 'offset', - SLA: 'sla', - SORT: 'sort', - START_DATE: 'beginning', - UOM: 'uom', - USAGE: 'usage' + ...rhsmConstants.RHSM_API_QUERY_SET_INVENTORY_TYPES }; /** @@ -357,7 +352,7 @@ const RHSM_API_QUERY_SET_INVENTORY_GUESTS_TYPES = { * LIMIT: string}} */ const RHSM_API_QUERY_SET_INVENTORY_SUBSCRIPTIONS_TYPES = { - ...RHSM_API_QUERY_SET_INVENTORY_TYPES + ...rhsmConstants.RHSM_API_QUERY_SET_INVENTORY_TYPES }; /**