summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTristan Cacqueray <tdecacqu@redhat.com>2018-12-02 05:48:04 +0000
committerTristan Cacqueray <tdecacqu@redhat.com>2018-12-02 05:56:38 +0000
commitf312f68ec6024d40ccc38b9aa955d1c525ef8783 (patch)
tree496762a662f35870c699de4db73a78989207c56d
parent17144c2a4637bd5334f3ba3b3f3f10424b345105 (diff)
web: add error reducer and info toast notification
This change adds a new error reducer to manage error from API calls. The info actions retries failed info request after 5 seconds. Change-Id: Ieb2b66a2847650788d9bf68080ab208855941f24
Notes
Notes (review): Code-Review+1: Artem Goncharov <artem.goncharov@gmail.com> Code-Review+2: Monty Taylor <mordred@inaugust.com> Code-Review+2: Tobias Henkel <tobias.henkel@bmw.de> Workflow+1: Monty Taylor <mordred@inaugust.com> Verified+2: Zuul Submitted-by: Zuul Submitted-at: Thu, 06 Dec 2018 20:42:51 +0000 Reviewed-on: https://review.openstack.org/621387 Project: openstack-infra/zuul Branch: refs/heads/master
-rw-r--r--web/package.json2
-rw-r--r--web/src/App.jsx28
-rw-r--r--web/src/actions/errors.js42
-rw-r--r--web/src/actions/info.js5
-rw-r--r--web/src/reducers/errors.js47
-rw-r--r--web/src/reducers/index.js2
-rw-r--r--web/yarn.lock10
7 files changed, 132 insertions, 4 deletions
diff --git a/web/package.json b/web/package.json
index 1aa81cb..97d1be9 100644
--- a/web/package.json
+++ b/web/package.json
@@ -8,7 +8,9 @@
8 "private": true, 8 "private": true,
9 "dependencies": { 9 "dependencies": {
10 "axios": "^0.18.0", 10 "axios": "^0.18.0",
11 "immutability-helper": "^2.8.1",
11 "lodash": "^4.17.10", 12 "lodash": "^4.17.10",
13 "moment": "^2.22.2",
12 "patternfly-react": "^2.13.1", 14 "patternfly-react": "^2.13.1",
13 "prop-types": "^15.6.2", 15 "prop-types": "^15.6.2",
14 "react": "^16.4.2", 16 "react": "^16.4.2",
diff --git a/web/src/App.jsx b/web/src/App.jsx
index 5ca9f9b..b2488a9 100644
--- a/web/src/App.jsx
+++ b/web/src/App.jsx
@@ -25,16 +25,21 @@ import {
25 Masthead, 25 Masthead,
26 Notification, 26 Notification,
27 NotificationDrawer, 27 NotificationDrawer,
28 TimedToastNotification,
29 ToastNotificationList,
28} from 'patternfly-react' 30} from 'patternfly-react'
31import * as moment from 'moment'
29 32
30import logo from './images/logo.png' 33import logo from './images/logo.png'
31import { routes } from './routes' 34import { routes } from './routes'
32import { fetchConfigErrorsAction } from './actions/configErrors' 35import { fetchConfigErrorsAction } from './actions/configErrors'
33import { setTenantAction } from './actions/tenant' 36import { setTenantAction } from './actions/tenant'
37import { clearError } from './actions/errors'
34 38
35 39
36class App extends React.Component { 40class App extends React.Component {
37 static propTypes = { 41 static propTypes = {
42 errors: PropTypes.array,
38 configErrors: PropTypes.array, 43 configErrors: PropTypes.array,
39 info: PropTypes.object, 44 info: PropTypes.object,
40 tenant: PropTypes.object, 45 tenant: PropTypes.object,
@@ -149,6 +154,25 @@ class App extends React.Component {
149 } 154 }
150 } 155 }
151 156
157 renderErrors = (errors) => {
158 return (
159 <ToastNotificationList>
160 {errors.map(error => (
161 <TimedToastNotification
162 key={error.id}
163 type='error'
164 onDismiss={() => {this.props.dispatch(clearError(error.id))}}
165 >
166 <span title={moment(error.date).format()}>
167 <strong>{error.text}</strong> ({error.status})&nbsp;
168 {error.url}
169 </span>
170 </TimedToastNotification>
171 ))}
172 </ToastNotificationList>
173 )
174 }
175
152 renderConfigErrors = (configErrors) => { 176 renderConfigErrors = (configErrors) => {
153 const { history } = this.props 177 const { history } = this.props
154 const errors = [] 178 const errors = []
@@ -207,7 +231,7 @@ class App extends React.Component {
207 231
208 render() { 232 render() {
209 const { menuCollapsed, showErrors } = this.state 233 const { menuCollapsed, showErrors } = this.state
210 const { tenant, configErrors } = this.props 234 const { errors, configErrors, tenant } = this.props
211 235
212 return ( 236 return (
213 <React.Fragment> 237 <React.Fragment>
@@ -252,6 +276,7 @@ class App extends React.Component {
252 </div> 276 </div>
253 )} 277 )}
254 </Masthead> 278 </Masthead>
279 {errors.length > 0 && this.renderErrors(errors)}
255 <div className='container-fluid container-cards-pf'> 280 <div className='container-fluid container-cards-pf'>
256 {this.renderContent()} 281 {this.renderContent()}
257 </div> 282 </div>
@@ -263,6 +288,7 @@ class App extends React.Component {
263// This connect the info state from the store to the info property of the App. 288// This connect the info state from the store to the info property of the App.
264export default withRouter(connect( 289export default withRouter(connect(
265 state => ({ 290 state => ({
291 errors: state.errors,
266 configErrors: state.configErrors, 292 configErrors: state.configErrors,
267 info: state.info, 293 info: state.info,
268 tenant: state.tenant 294 tenant: state.tenant
diff --git a/web/src/actions/errors.js b/web/src/actions/errors.js
new file mode 100644
index 0000000..e113121
--- /dev/null
+++ b/web/src/actions/errors.js
@@ -0,0 +1,42 @@
1// Copyright 2018 Red Hat, Inc
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may
4// not use this file except in compliance with the License. You may obtain
5// a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12// License for the specific language governing permissions and limitations
13// under the License.
14
15export const ADD_ERROR = 'ADD_ERROR'
16export const CLEAR_ERROR = 'CLEAR_ERROR'
17export const CLEAR_ERRORS = 'CLEAR_ERRORS'
18
19let errorId = 0
20
21export const addError = error => ({
22 type: ADD_ERROR,
23 id: errorId++,
24 error
25})
26
27export const addApiError = error => (
28 addError({
29 url: error.request.responseURL,
30 status: error.response.status,
31 text: error.response.statusText,
32 })
33)
34
35export const clearError = id => ({
36 type: CLEAR_ERROR,
37 id
38})
39
40export const clearErrors = () => ({
41 type: CLEAR_ERRORS
42})
diff --git a/web/src/actions/info.js b/web/src/actions/info.js
index 81e9b4a..a28c626 100644
--- a/web/src/actions/info.js
+++ b/web/src/actions/info.js
@@ -36,7 +36,10 @@ const fetchInfo = () => dispatch => {
36 dispatch(fetchInfoRequest()) 36 dispatch(fetchInfoRequest())
37 return API.fetchInfo() 37 return API.fetchInfo()
38 .then(response => dispatch(fetchInfoSuccess(response.data))) 38 .then(response => dispatch(fetchInfoSuccess(response.data)))
39 .catch(error => dispatch(fetchInfoFail(error))) 39 .catch(error => {
40 dispatch(fetchInfoFail(error))
41 setTimeout(() => {dispatch(fetchInfo())}, 5000)
42 })
40} 43}
41 44
42const shouldFetchInfo = state => { 45const shouldFetchInfo = state => {
diff --git a/web/src/reducers/errors.js b/web/src/reducers/errors.js
new file mode 100644
index 0000000..165bb02
--- /dev/null
+++ b/web/src/reducers/errors.js
@@ -0,0 +1,47 @@
1// Copyright 2018 Red Hat, Inc
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may
4// not use this file except in compliance with the License. You may obtain
5// a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12// License for the specific language governing permissions and limitations
13// under the License.
14
15import update from 'immutability-helper'
16
17import {
18 ADD_ERROR,
19 CLEAR_ERROR,
20 CLEAR_ERRORS,
21 addApiError,
22} from '../actions/errors'
23
24
25export default (state = [], action) => {
26 // Intercept API failure
27 if (action.error && action.type.match(/.*_FETCH_FAIL$/)) {
28 action = addApiError(action.error)
29 }
30 switch (action.type) {
31 case ADD_ERROR:
32 if (state.filter(error => (
33 error.url === action.error.url &&
34 error.status === action.error.status)).length > 0)
35 return state
36 action.error.id = action.id
37 action.error.date = Date.now()
38 return update(state, {$push: [action.error]})
39 case CLEAR_ERROR:
40 return update(state, {$splice: [[state.indexOf(
41 state.filter(item => (item.id === action.id))[0]), 1]]})
42 case CLEAR_ERRORS:
43 return []
44 default:
45 return state
46 }
47}
diff --git a/web/src/reducers/index.js b/web/src/reducers/index.js
index 6f287ae..ab6aab8 100644
--- a/web/src/reducers/index.js
+++ b/web/src/reducers/index.js
@@ -15,12 +15,14 @@
15import { combineReducers } from 'redux' 15import { combineReducers } from 'redux'
16 16
17import configErrors from './configErrors' 17import configErrors from './configErrors'
18import errors from './errors'
18import info from './info' 19import info from './info'
19import tenant from './tenant' 20import tenant from './tenant'
20 21
21const reducers = { 22const reducers = {
22 info, 23 info,
23 configErrors, 24 configErrors,
25 errors,
24 tenant, 26 tenant,
25} 27}
26 28
diff --git a/web/yarn.lock b/web/yarn.lock
index 2cee873..54e5a3c 100644
--- a/web/yarn.lock
+++ b/web/yarn.lock
@@ -3762,6 +3762,12 @@ ignore@^4.0.2:
3762 version "4.0.6" 3762 version "4.0.6"
3763 resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" 3763 resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
3764 3764
3765immutability-helper@^2.8.1:
3766 version "2.8.1"
3767 resolved "https://registry.yarnpkg.com/immutability-helper/-/immutability-helper-2.8.1.tgz#3c5ec05fcd83676bfae7146f319595243ad904f4"
3768 dependencies:
3769 invariant "^2.2.0"
3770
3765import-lazy@^2.1.0: 3771import-lazy@^2.1.0:
3766 version "2.1.0" 3772 version "2.1.0"
3767 resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" 3773 resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43"
@@ -3857,7 +3863,7 @@ interpret@^1.0.0:
3857 version "1.1.0" 3863 version "1.1.0"
3858 resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614" 3864 resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614"
3859 3865
3860invariant@^2.0.0, invariant@^2.1.0, invariant@^2.2.1, invariant@^2.2.2, invariant@^2.2.4: 3866invariant@^2.0.0, invariant@^2.1.0, invariant@^2.2.0, invariant@^2.2.1, invariant@^2.2.2, invariant@^2.2.4:
3861 version "2.2.4" 3867 version "2.2.4"
3862 resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" 3868 resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
3863 dependencies: 3869 dependencies:
@@ -5027,7 +5033,7 @@ moment-timezone@^0.4.0, moment-timezone@^0.4.1:
5027 dependencies: 5033 dependencies:
5028 moment ">= 2.6.0" 5034 moment ">= 2.6.0"
5029 5035
5030"moment@>= 2.6.0", moment@^2.10, moment@^2.19.1: 5036"moment@>= 2.6.0", moment@^2.10, moment@^2.19.1, moment@^2.22.2:
5031 version "2.22.2" 5037 version "2.22.2"
5032 resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66" 5038 resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66"
5033 5039