diff --git a/frontend/interfaces/errors500.js b/frontend/interfaces/errors500.js new file mode 100644 index 00000000000..39b02123c22 --- /dev/null +++ b/frontend/interfaces/errors500.js @@ -0,0 +1,6 @@ +import PropTypes from 'prop-types'; + +export default PropTypes.shape({ + http_status: PropTypes.number, + base: PropTypes.string, +}); diff --git a/frontend/pages/Kolide500/Kolide500.jsx b/frontend/pages/Kolide500/Kolide500.jsx index dd9282f41ec..a0772c432c3 100644 --- a/frontend/pages/Kolide500/Kolide500.jsx +++ b/frontend/pages/Kolide500/Kolide500.jsx @@ -1,12 +1,72 @@ import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import { noop } from 'lodash'; +import { resetErrors } from 'redux/nodes/errors500/actions'; +import errorsInterface from 'interfaces/errors500'; import kolideLogo from '../../../assets/images/kolide-logo-condensed.svg'; import gopher from '../../../assets/images/500.svg'; const baseClass = 'kolide-500'; -class Kolide404 extends Component { +class Kolide500 extends Component { + static propTypes = { + errors: errorsInterface, + dispatch: PropTypes.func, + }; + + static defaultProps = { + dispatch: noop, + }; + + constructor (props) { + super(props); + + this.state = { + showErrorMessage: false, + }; + } + + componentWillUnmount() { + const { dispatch } = this.props; + dispatch(resetErrors()); + } + + onShowErrorMessage = () => { + this.setState({ showErrorMessage: true }); + } + + renderError = () => { + const { errors } = this.props; + const errorMessage = errors ? errors.base : null; + const { showErrorMessage } = this.state; + const { onShowErrorMessage } = this; + + if (errorMessage && !showErrorMessage) { + // We only show the button when errorMessage exists + // and showErrorMessage is set to false + return ( + + ); + } + + if (errorMessage && showErrorMessage) { + // We only show the error message when errorMessage exists + // and showErrorMessage is set to true + return ( +
+

{errorMessage}

+
+ ); + } + + return false; + } + render () { + const { renderError } = this; + return (
@@ -18,10 +78,17 @@ class Kolide404 extends Component {

Uh oh!

Error 500

Something went wrong on our end.

-

We have alerted the engineers and they are working on a solution.

+ {renderError()} +

Please file an issue if you believe this is a bug.

+ + File an issue +
-

Need assistance? File an issue.

@@ -29,4 +96,11 @@ class Kolide404 extends Component { } } -export default Kolide404; +const mapStateToProps = (state) => { + const { errors } = state.errors500; + return { + errors, + }; +}; + +export default connect(mapStateToProps)(Kolide500); diff --git a/frontend/pages/Kolide500/_styles.scss b/frontend/pages/Kolide500/_styles.scss index 62f835200e7..775818d2cef 100644 --- a/frontend/pages/Kolide500/_styles.scss +++ b/frontend/pages/Kolide500/_styles.scss @@ -36,6 +36,10 @@ } } + .error-message-container { + display: inline; + } + main { text-align: center; diff --git a/frontend/redux/nodes/errors500/actions.js b/frontend/redux/nodes/errors500/actions.js new file mode 100644 index 00000000000..2b4c2e4f379 --- /dev/null +++ b/frontend/redux/nodes/errors500/actions.js @@ -0,0 +1,7 @@ +export const RESET_ERRORS = 'RESET_ERRORS'; + +export const resetErrors = () => { + return { + type: RESET_ERRORS, + }; +}; diff --git a/frontend/redux/nodes/errors500/reducer.js b/frontend/redux/nodes/errors500/reducer.js new file mode 100644 index 00000000000..03fe16f081a --- /dev/null +++ b/frontend/redux/nodes/errors500/reducer.js @@ -0,0 +1,22 @@ +import { + RESET_ERRORS, +} from './actions'; + +const initialState = { + errors: null, +}; + +const reducer = (state = initialState, { type, payload }) => { + if (payload && payload.errors) { + return { + errors: payload.errors, + }; + } else if (type === RESET_ERRORS) { + return { + errors: null, + }; + } + return state; +}; + +export default reducer; diff --git a/frontend/redux/nodes/errors500/reducer.tests.js b/frontend/redux/nodes/errors500/reducer.tests.js new file mode 100644 index 00000000000..6adada62282 --- /dev/null +++ b/frontend/redux/nodes/errors500/reducer.tests.js @@ -0,0 +1,39 @@ +import expect from 'expect'; + +import reducer from './reducer'; + +describe('Errors - reducer', () => { + it('Updates state with errors object when an action that has a payload with an errors object is dispatched', () => { + const payload = { + errors: { + base: "inserting pack: Error 1136: Column count doesn't match value count at row 1", + http_status: 500, + }, + }; + const packsCreateFailureAction = { type: 'packs_CREATE_FAILURE', payload }; + const initialState = { + errors: null, + }; + const newState = reducer(initialState, packsCreateFailureAction); + + expect(newState).toEqual({ + errors: { + base: "inserting pack: Error 1136: Column count doesn't match value count at row 1", + http_status: 500, + }, + }); + }); + + it('Updates state by setting errors to null when the RESET_ERRORS action is dipatched', () => { + const errorsState = { + errors: { + base: "inserting pack: Error 1136: Column count doesn't match value count at row 1", + http_status: 500, + }, + }; + const newState = reducer(errorsState, { type: 'RESET_ERRORS' }); + expect(newState).toEqual({ + errors: null, + }); + }); +}); diff --git a/frontend/redux/reducers.js b/frontend/redux/reducers.js index 87426a618f1..fb4430031e2 100644 --- a/frontend/redux/reducers.js +++ b/frontend/redux/reducers.js @@ -6,6 +6,7 @@ import app from './nodes/app/reducer'; import auth from './nodes/auth/reducer'; import components from './nodes/components/reducer'; import entities from './nodes/entities/reducer'; +import errors500 from './nodes/errors500/reducer'; import notifications from './nodes/notifications/reducer'; import persistentFlash from './nodes/persistent_flash/reducer'; import redirectLocation from './nodes/redirectLocation/reducer'; @@ -15,6 +16,7 @@ export default combineReducers({ auth, components, entities, + errors500, loadingBar: loadingBarReducer, notifications, persistentFlash,