From 3b91169acdfdd778c24cbc9d6e1d749430ea918b Mon Sep 17 00:00:00 2001 From: Honza Pokorny Date: Fri, 9 Feb 2018 12:05:05 -0400 Subject: [PATCH] Add flavor fetching via Nova API This patch support for fetching Nova flavors via its REST API. We're including all of the necessary pieces including reducer, selector, etc. Change-Id: I9535fa8254039ef564d62b653934267b2380ce7c Depends-On: Ic6df58947bb2cb1e183b5c88ed8d287191e5ee07 --- src/js/actions/FlavorsActions.js | 59 ++++++++++++++++++++++++++ src/js/constants/FlavorsConstants.js | 23 ++++++++++ src/js/immutableRecords/flavors.js | 28 +++++++++++++ src/js/normalizrSchemas/flavors.js | 23 ++++++++++ src/js/reducers/appReducer.js | 2 + src/js/reducers/flavorsReducer.js | 43 +++++++++++++++++++ src/js/selectors/flavors.js | 23 ++++++++++ src/js/services/NovaApiService.js | 63 ++++++++++++++++++++++++++++ src/js/services/errors/index.js | 9 +++- 9 files changed, 272 insertions(+), 1 deletion(-) create mode 100644 src/js/actions/FlavorsActions.js create mode 100644 src/js/constants/FlavorsConstants.js create mode 100644 src/js/immutableRecords/flavors.js create mode 100644 src/js/normalizrSchemas/flavors.js create mode 100644 src/js/reducers/flavorsReducer.js create mode 100644 src/js/selectors/flavors.js create mode 100644 src/js/services/NovaApiService.js diff --git a/src/js/actions/FlavorsActions.js b/src/js/actions/FlavorsActions.js new file mode 100644 index 00000000..86a3565c --- /dev/null +++ b/src/js/actions/FlavorsActions.js @@ -0,0 +1,59 @@ +/** + * Copyright 2018 Red Hat Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +import { normalize } from 'normalizr'; + +import { flavorSchema } from '../normalizrSchemas/flavors'; +import { handleErrors } from './ErrorActions'; +import NovaApiService from '../services/NovaApiService'; +import FlavorsConstants from '../constants/FlavorsConstants'; + +export default { + fetchFlavorsPending() { + return { + type: FlavorsConstants.FETCH_FLAVORS_PENDING + }; + }, + + fetchFlavorsSuccess(flavors) { + return { + type: FlavorsConstants.FETCH_FLAVORS_SUCCESS, + payload: flavors + }; + }, + + fetchFlavorsFailed() { + return { + type: FlavorsConstants.FETCH_FLAVORS_FAILED + }; + }, + + fetchFlavors() { + return dispatch => { + dispatch(this.fetchFlavorsPending()); + dispatch(NovaApiService.getFlavors()) + .then(response => { + const flavors = normalize(response.flavors, [flavorSchema]).entities + .flavors; + dispatch(this.fetchFlavorsSuccess(flavors)); + }) + .catch(error => { + dispatch(handleErrors(error, 'Flavors could not be loaded.')); + dispatch(this.fetchFlavorsFailed()); + }); + }; + } +}; diff --git a/src/js/constants/FlavorsConstants.js b/src/js/constants/FlavorsConstants.js new file mode 100644 index 00000000..2483c0a8 --- /dev/null +++ b/src/js/constants/FlavorsConstants.js @@ -0,0 +1,23 @@ +/** + * Copyright 2018 Red Hat Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +import keyMirror from 'keymirror'; + +export default keyMirror({ + FETCH_FLAVORS_PENDING: null, + FETCH_FLAVORS_FAILED: null, + FETCH_FLAVORS_SUCCESS: null +}); diff --git a/src/js/immutableRecords/flavors.js b/src/js/immutableRecords/flavors.js new file mode 100644 index 00000000..efbcbcf1 --- /dev/null +++ b/src/js/immutableRecords/flavors.js @@ -0,0 +1,28 @@ +/** + * Copyright 2018 Red Hat Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +import { Map, Record } from 'immutable'; + +export const FlavorsState = Record({ + isFetching: false, + isLoaded: false, + flavors: Map() +}); + +export const Flavor = Record({ + name: undefined, + id: undefined +}); diff --git a/src/js/normalizrSchemas/flavors.js b/src/js/normalizrSchemas/flavors.js new file mode 100644 index 00000000..90549ca4 --- /dev/null +++ b/src/js/normalizrSchemas/flavors.js @@ -0,0 +1,23 @@ +/** + * Copyright 2018 Red Hat Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +import { schema } from 'normalizr'; + +export const flavorSchema = new schema.Entity( + 'flavors', + {}, + { idAttribute: 'id' } +); diff --git a/src/js/reducers/appReducer.js b/src/js/reducers/appReducer.js index 21844d68..b231024a 100644 --- a/src/js/reducers/appReducer.js +++ b/src/js/reducers/appReducer.js @@ -20,6 +20,7 @@ import { reducer as formReducer } from 'redux-form'; import appConfig from './appConfig'; import environmentConfigurationReducer from './environmentConfigurationReducer'; import filtersReducer from './filtersReducer'; +import flavorsReducer from './flavorsReducer'; import i18nReducer from './i18nReducer'; import loggerReducer from './loggerReducer'; import loginReducer from './loginReducer'; @@ -39,6 +40,7 @@ const appReducer = combineReducers({ environmentConfiguration: environmentConfigurationReducer, executions: workflowExecutionsReducer, filters: filtersReducer, + flavors: flavorsReducer, i18n: i18nReducer, logger: loggerReducer, login: loginReducer, diff --git a/src/js/reducers/flavorsReducer.js b/src/js/reducers/flavorsReducer.js new file mode 100644 index 00000000..c9ffeeec --- /dev/null +++ b/src/js/reducers/flavorsReducer.js @@ -0,0 +1,43 @@ +/** + * Copyright 2018 Red Hat Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +import { fromJS } from 'immutable'; + +import FlavorsConstants from '../constants/FlavorsConstants'; +import { FlavorsState, Flavor } from '../immutableRecords/flavors'; + +const initialState = new FlavorsState(); + +export default function flavorsReducer(state = initialState, action) { + switch (action.type) { + case FlavorsConstants.FETCH_FLAVORS_PENDING: + return state.set('isFetching', true); + + case FlavorsConstants.FETCH_FLAVORS_FAILED: + return state.set('isFetching', false); + + case FlavorsConstants.FETCH_FLAVORS_SUCCESS: { + const flavors = fromJS(action.payload).map(flavor => new Flavor(flavor)); + return state + .set('flavors', flavors) + .set('isLoaded', true) + .set('isFetching', false); + } + + default: + return state; + } +} diff --git a/src/js/selectors/flavors.js b/src/js/selectors/flavors.js new file mode 100644 index 00000000..a7374a4a --- /dev/null +++ b/src/js/selectors/flavors.js @@ -0,0 +1,23 @@ +/** + * Copyright 2018 Red Hat Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +import { createSelector } from 'reselect'; + +const flavors = state => state.flavors; + +export const getFlavors = createSelector([flavors], flavors => + flavors.flavors.sortBy(f => f.name) +); diff --git a/src/js/services/NovaApiService.js b/src/js/services/NovaApiService.js new file mode 100644 index 00000000..722a7aa7 --- /dev/null +++ b/src/js/services/NovaApiService.js @@ -0,0 +1,63 @@ +/** + * Copyright 2018 Red Hat Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +import axios from 'axios'; +import when from 'when'; + +import { AuthenticationError, NovaApiError, ConnectionError } from './errors'; +import { getAuthTokenId, getServiceUrl } from '../selectors/auth'; + +class NovaApiService { + defaultRequest(path, additionalAttributes) { + return (dispatch, getState) => + axios( + Object.assign( + { + baseURL: getServiceUrl(getState(), 'nova'), + url: path, + method: 'GET', + headers: { + 'X-Auth-Token': getAuthTokenId(getState()) + } + }, + additionalAttributes + ) + ); + } + + getFlavors() { + return dispatch => + dispatch(this.defaultRequest('/flavors')) + .then(response => response.data) + .catch(handleErrors); + } +} + +const handleErrors = e => { + if (e.response && e.response.status === 401) { + return when.reject(new AuthenticationError(e)); + } else if (e.response) { + return when.reject(new NovaApiError(e)); + } else if (e.request) { + return when.reject( + new ConnectionError('Connection to Nova API could not be established', e) + ); + } else { + return when.reject(e); + } +}; + +export default new NovaApiService(); diff --git a/src/js/services/errors/index.js b/src/js/services/errors/index.js index 311df729..75efe880 100644 --- a/src/js/services/errors/index.js +++ b/src/js/services/errors/index.js @@ -84,6 +84,12 @@ class KeystoneApiError extends BaseAxiosError { } } +class NovaApiError extends BaseAxiosError { + constructor(e) { + super('NovaApiError', e.response.data.error.message, e); + } +} + export { BaseAxiosError, AuthenticationError, @@ -94,5 +100,6 @@ export { IronicApiError, IronicInspectorApiError, HeatApiError, - KeystoneApiError + KeystoneApiError, + NovaApiError };