Browse Source

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
tags/3.4.0
Tristan Cacqueray 5 months ago
parent
commit
f312f68ec6
7 changed files with 132 additions and 4 deletions
  1. 2
    0
      web/package.json
  2. 27
    1
      web/src/App.jsx
  3. 42
    0
      web/src/actions/errors.js
  4. 4
    1
      web/src/actions/info.js
  5. 47
    0
      web/src/reducers/errors.js
  6. 2
    0
      web/src/reducers/index.js
  7. 8
    2
      web/yarn.lock

+ 2
- 0
web/package.json View File

@@ -8,7 +8,9 @@
8 8
   "private": true,
9 9
   "dependencies": {
10 10
     "axios": "^0.18.0",
11
+    "immutability-helper": "^2.8.1",
11 12
     "lodash": "^4.17.10",
13
+    "moment": "^2.22.2",
12 14
     "patternfly-react": "^2.13.1",
13 15
     "prop-types": "^15.6.2",
14 16
     "react": "^16.4.2",

+ 27
- 1
web/src/App.jsx View File

@@ -25,16 +25,21 @@ import {
25 25
   Masthead,
26 26
   Notification,
27 27
   NotificationDrawer,
28
+  TimedToastNotification,
29
+  ToastNotificationList,
28 30
 } from 'patternfly-react'
31
+import * as moment from 'moment'
29 32
 
30 33
 import logo from './images/logo.png'
31 34
 import { routes } from './routes'
32 35
 import { fetchConfigErrorsAction } from './actions/configErrors'
33 36
 import { setTenantAction } from './actions/tenant'
37
+import { clearError } from './actions/errors'
34 38
 
35 39
 
36 40
 class App extends React.Component {
37 41
   static propTypes = {
42
+    errors: PropTypes.array,
38 43
     configErrors: PropTypes.array,
39 44
     info: PropTypes.object,
40 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 176
   renderConfigErrors = (configErrors) => {
153 177
     const { history } = this.props
154 178
     const errors = []
@@ -207,7 +231,7 @@ class App extends React.Component {
207 231
 
208 232
   render() {
209 233
     const { menuCollapsed, showErrors } = this.state
210
-    const { tenant, configErrors } = this.props
234
+    const { errors, configErrors, tenant } = this.props
211 235
 
212 236
     return (
213 237
       <React.Fragment>
@@ -252,6 +276,7 @@ class App extends React.Component {
252 276
             </div>
253 277
           )}
254 278
         </Masthead>
279
+        {errors.length > 0 && this.renderErrors(errors)}
255 280
         <div className='container-fluid container-cards-pf'>
256 281
           {this.renderContent()}
257 282
         </div>
@@ -263,6 +288,7 @@ class App extends React.Component {
263 288
 // This connect the info state from the store to the info property of the App.
264 289
 export default withRouter(connect(
265 290
   state => ({
291
+    errors: state.errors,
266 292
     configErrors: state.configErrors,
267 293
     info: state.info,
268 294
     tenant: state.tenant

+ 42
- 0
web/src/actions/errors.js View File

@@ -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
+
15
+export const ADD_ERROR = 'ADD_ERROR'
16
+export const CLEAR_ERROR = 'CLEAR_ERROR'
17
+export const CLEAR_ERRORS = 'CLEAR_ERRORS'
18
+
19
+let errorId = 0
20
+
21
+export const addError = error => ({
22
+  type: ADD_ERROR,
23
+  id: errorId++,
24
+  error
25
+})
26
+
27
+export const addApiError = error => (
28
+  addError({
29
+    url: error.request.responseURL,
30
+    status: error.response.status,
31
+    text: error.response.statusText,
32
+  })
33
+)
34
+
35
+export const clearError = id => ({
36
+  type: CLEAR_ERROR,
37
+  id
38
+})
39
+
40
+export const clearErrors = () => ({
41
+  type: CLEAR_ERRORS
42
+})

+ 4
- 1
web/src/actions/info.js View File

@@ -36,7 +36,10 @@ const fetchInfo = () => dispatch => {
36 36
   dispatch(fetchInfoRequest())
37 37
   return API.fetchInfo()
38 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
 
42 45
 const shouldFetchInfo = state => {

+ 47
- 0
web/src/reducers/errors.js View File

@@ -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
+
15
+import update from 'immutability-helper'
16
+
17
+import {
18
+  ADD_ERROR,
19
+  CLEAR_ERROR,
20
+  CLEAR_ERRORS,
21
+  addApiError,
22
+} from '../actions/errors'
23
+
24
+
25
+export 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
+}

+ 2
- 0
web/src/reducers/index.js View File

@@ -15,12 +15,14 @@
15 15
 import { combineReducers } from 'redux'
16 16
 
17 17
 import configErrors from './configErrors'
18
+import errors from './errors'
18 19
 import info from './info'
19 20
 import tenant from './tenant'
20 21
 
21 22
 const reducers = {
22 23
   info,
23 24
   configErrors,
25
+  errors,
24 26
   tenant,
25 27
 }
26 28
 

+ 8
- 2
web/yarn.lock View File

@@ -3762,6 +3762,12 @@ ignore@^4.0.2:
3762 3762
   version "4.0.6"
3763 3763
   resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
3764 3764
 
3765
+immutability-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
+
3765 3771
 import-lazy@^2.1.0:
3766 3772
   version "2.1.0"
3767 3773
   resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43"
@@ -3857,7 +3863,7 @@ interpret@^1.0.0:
3857 3863
   version "1.1.0"
3858 3864
   resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614"
3859 3865
 
3860
-invariant@^2.0.0, invariant@^2.1.0, invariant@^2.2.1, invariant@^2.2.2, invariant@^2.2.4:
3866
+invariant@^2.0.0, invariant@^2.1.0, invariant@^2.2.0, invariant@^2.2.1, invariant@^2.2.2, invariant@^2.2.4:
3861 3867
   version "2.2.4"
3862 3868
   resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
3863 3869
   dependencies:
@@ -5027,7 +5033,7 @@ moment-timezone@^0.4.0, moment-timezone@^0.4.1:
5027 5033
   dependencies:
5028 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 5037
   version "2.22.2"
5032 5038
   resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66"
5033 5039
 

Loading…
Cancel
Save