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
This commit is contained in:
Tristan Cacqueray 2018-12-02 05:48:04 +00:00
parent 17144c2a46
commit f312f68ec6
7 changed files with 132 additions and 4 deletions

View File

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

View File

@ -25,16 +25,21 @@ import {
Masthead,
Notification,
NotificationDrawer,
TimedToastNotification,
ToastNotificationList,
} from 'patternfly-react'
import * as moment from 'moment'
import logo from './images/logo.png'
import { routes } from './routes'
import { fetchConfigErrorsAction } from './actions/configErrors'
import { setTenantAction } from './actions/tenant'
import { clearError } from './actions/errors'
class App extends React.Component {
static propTypes = {
errors: PropTypes.array,
configErrors: PropTypes.array,
info: PropTypes.object,
tenant: PropTypes.object,
@ -149,6 +154,25 @@ class App extends React.Component {
}
}
renderErrors = (errors) => {
return (
<ToastNotificationList>
{errors.map(error => (
<TimedToastNotification
key={error.id}
type='error'
onDismiss={() => {this.props.dispatch(clearError(error.id))}}
>
<span title={moment(error.date).format()}>
<strong>{error.text}</strong> ({error.status})&nbsp;
{error.url}
</span>
</TimedToastNotification>
))}
</ToastNotificationList>
)
}
renderConfigErrors = (configErrors) => {
const { history } = this.props
const errors = []
@ -207,7 +231,7 @@ class App extends React.Component {
render() {
const { menuCollapsed, showErrors } = this.state
const { tenant, configErrors } = this.props
const { errors, configErrors, tenant } = this.props
return (
<React.Fragment>
@ -252,6 +276,7 @@ class App extends React.Component {
</div>
)}
</Masthead>
{errors.length > 0 && this.renderErrors(errors)}
<div className='container-fluid container-cards-pf'>
{this.renderContent()}
</div>
@ -263,6 +288,7 @@ class App extends React.Component {
// This connect the info state from the store to the info property of the App.
export default withRouter(connect(
state => ({
errors: state.errors,
configErrors: state.configErrors,
info: state.info,
tenant: state.tenant

42
web/src/actions/errors.js Normal file
View File

@ -0,0 +1,42 @@
// 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.
export const ADD_ERROR = 'ADD_ERROR'
export const CLEAR_ERROR = 'CLEAR_ERROR'
export const CLEAR_ERRORS = 'CLEAR_ERRORS'
let errorId = 0
export const addError = error => ({
type: ADD_ERROR,
id: errorId++,
error
})
export const addApiError = error => (
addError({
url: error.request.responseURL,
status: error.response.status,
text: error.response.statusText,
})
)
export const clearError = id => ({
type: CLEAR_ERROR,
id
})
export const clearErrors = () => ({
type: CLEAR_ERRORS
})

View File

@ -36,7 +36,10 @@ const fetchInfo = () => dispatch => {
dispatch(fetchInfoRequest())
return API.fetchInfo()
.then(response => dispatch(fetchInfoSuccess(response.data)))
.catch(error => dispatch(fetchInfoFail(error)))
.catch(error => {
dispatch(fetchInfoFail(error))
setTimeout(() => {dispatch(fetchInfo())}, 5000)
})
}
const shouldFetchInfo = state => {

View File

@ -0,0 +1,47 @@
// 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 update from 'immutability-helper'
import {
ADD_ERROR,
CLEAR_ERROR,
CLEAR_ERRORS,
addApiError,
} from '../actions/errors'
export default (state = [], action) => {
// Intercept API failure
if (action.error && action.type.match(/.*_FETCH_FAIL$/)) {
action = addApiError(action.error)
}
switch (action.type) {
case ADD_ERROR:
if (state.filter(error => (
error.url === action.error.url &&
error.status === action.error.status)).length > 0)
return state
action.error.id = action.id
action.error.date = Date.now()
return update(state, {$push: [action.error]})
case CLEAR_ERROR:
return update(state, {$splice: [[state.indexOf(
state.filter(item => (item.id === action.id))[0]), 1]]})
case CLEAR_ERRORS:
return []
default:
return state
}
}

View File

@ -15,12 +15,14 @@
import { combineReducers } from 'redux'
import configErrors from './configErrors'
import errors from './errors'
import info from './info'
import tenant from './tenant'
const reducers = {
info,
configErrors,
errors,
tenant,
}

View File

@ -3762,6 +3762,12 @@ ignore@^4.0.2:
version "4.0.6"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
immutability-helper@^2.8.1:
version "2.8.1"
resolved "https://registry.yarnpkg.com/immutability-helper/-/immutability-helper-2.8.1.tgz#3c5ec05fcd83676bfae7146f319595243ad904f4"
dependencies:
invariant "^2.2.0"
import-lazy@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43"
@ -3857,7 +3863,7 @@ interpret@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614"
invariant@^2.0.0, invariant@^2.1.0, invariant@^2.2.1, invariant@^2.2.2, invariant@^2.2.4:
invariant@^2.0.0, invariant@^2.1.0, invariant@^2.2.0, invariant@^2.2.1, invariant@^2.2.2, invariant@^2.2.4:
version "2.2.4"
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
dependencies:
@ -5027,7 +5033,7 @@ moment-timezone@^0.4.0, moment-timezone@^0.4.1:
dependencies:
moment ">= 2.6.0"
"moment@>= 2.6.0", moment@^2.10, moment@^2.19.1:
"moment@>= 2.6.0", moment@^2.10, moment@^2.19.1, moment@^2.22.2:
version "2.22.2"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66"