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 (
+
+ );
+ }
+
+ 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
+
@@ -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,