diff --git a/web/.eslintrc b/web/.eslintrc index 6afc1d202a..4527f2e9f2 100644 --- a/web/.eslintrc +++ b/web/.eslintrc @@ -23,3 +23,4 @@ env: jest/globals: true browser: true es6: true + node: true diff --git a/web/package.json b/web/package.json index 693f892f63..872f09406e 100644 --- a/web/package.json +++ b/web/package.json @@ -28,6 +28,7 @@ "react-scripts": "3.4.1", "react-select": "3.1.0", "redux": "^4.0.5", + "redux-immutable-state-invariant": "^2.1.0", "redux-thunk": "^2.3.0", "sockette": "^2.0.0", "swagger-ui": "^3.20.1", diff --git a/web/src/App.test.jsx b/web/src/App.test.jsx index ced7ac0e10..60c40d43c9 100644 --- a/web/src/App.test.jsx +++ b/web/src/App.test.jsx @@ -19,7 +19,7 @@ import { Link, BrowserRouter as Router } from 'react-router-dom' import { Provider } from 'react-redux' import { fetchInfoIfNeeded } from './actions/info' -import store from './store' +import configureStore from './store' import App from './App' import TenantsPage from './pages/Tenants' import StatusPage from './pages/Status' @@ -31,6 +31,7 @@ api.fetchStatus = jest.fn() api.fetchConfigErrors = jest.fn() api.fetchConfigErrors.mockImplementation(() => Promise.resolve({data: []})) +const store = configureStore() it('renders without crashing', () => { const div = document.createElement('div') diff --git a/web/src/api.js b/web/src/api.js index 619da3358a..df49278b2b 100644 --- a/web/src/api.js +++ b/web/src/api.js @@ -1,4 +1,3 @@ -/* global process */ // Copyright 2018 Red Hat, Inc // // Licensed under the Apache License, Version 2.0 (the "License"); you may diff --git a/web/src/containers/status/ChangePanel.test.jsx b/web/src/containers/status/ChangePanel.test.jsx index d432c01051..76a3051f8e 100644 --- a/web/src/containers/status/ChangePanel.test.jsx +++ b/web/src/containers/status/ChangePanel.test.jsx @@ -18,7 +18,7 @@ import { Link, BrowserRouter as Router } from 'react-router-dom' import { Provider } from 'react-redux' import { setTenantAction } from '../../actions/tenant' -import store from '../../store' +import configureStore from '../../store' import ChangePanel from './ChangePanel' @@ -31,6 +31,8 @@ const fakeChange = { }] } +const store = configureStore() + it('change panel render multi tenant links', () => { store.dispatch(setTenantAction('tenant-one', false)) const application = ReactTestUtils.renderIntoDocument( diff --git a/web/src/index.js b/web/src/index.js index 549e60c957..c0a1772aa0 100644 --- a/web/src/index.js +++ b/web/src/index.js @@ -37,7 +37,7 @@ import './pf4-migration.css' import { getHomepageUrl } from './api' import registerServiceWorker from './registerServiceWorker' import { fetchInfoIfNeeded } from './actions/info' -import store from './store' +import configureStore from './store' import App from './App' // Importing our custom css file after the App allows us to also overwrite the @@ -45,6 +45,8 @@ import App from './App' // is imported within the App). import './index.css' +const store = configureStore() + // Load info endpoint store.dispatch(fetchInfoIfNeeded()) diff --git a/web/src/registerServiceWorker.js b/web/src/registerServiceWorker.js index 483e5d54df..e6c8545d37 100644 --- a/web/src/registerServiceWorker.js +++ b/web/src/registerServiceWorker.js @@ -1,4 +1,3 @@ -/* global process */ // In production, we register a service worker to serve assets from local cache. // This lets the app load faster on subsequent visits in production, and gives diff --git a/web/src/store.dev.js b/web/src/store.dev.js new file mode 100644 index 0000000000..2c0f16a96b --- /dev/null +++ b/web/src/store.dev.js @@ -0,0 +1,39 @@ +// Copyright 2020 BMW Group +// +// 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 { applyMiddleware, compose, createStore } from 'redux' +import appReducer from './reducers' +import reduxImmutableStateInvariant from 'redux-immutable-state-invariant' +import thunk from 'redux-thunk' + +export default function configureStore(initialState) { + // Add support for Redux devtools + const composeEnhancers = + window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose + return createStore( + appReducer, + initialState, + // Warn us if we accidentially mutate state directly in the Redux store + // (only during development). + composeEnhancers( + applyMiddleware( + thunk, + // TODO (felix): Re-enable the status.status path once we know how to + // solve the weird state mutations that are done somewhere deep within + // the logic of the status page (or its child components). + reduxImmutableStateInvariant({ ignore: ['status.status'] }) + ) + ) + ) +} diff --git a/web/src/store.js b/web/src/store.js index ec1b8443b5..783b59031e 100644 --- a/web/src/store.js +++ b/web/src/store.js @@ -12,11 +12,9 @@ // License for the specific language governing permissions and limitations // under the License. -import { applyMiddleware, createStore } from 'redux' -import thunk from 'redux-thunk' - -import appReducers from './reducers' - -const store = createStore(appReducers, applyMiddleware(thunk)) - -export default store \ No newline at end of file +// Use CommonJS require so we can dynamically import during build-time. +if (process.env.NODE_ENV === 'production') { + module.exports = require('./store.prod') +} else { + module.exports = require('./store.dev') +} diff --git a/web/src/store.prod.js b/web/src/store.prod.js new file mode 100644 index 0000000000..4915ba63b0 --- /dev/null +++ b/web/src/store.prod.js @@ -0,0 +1,21 @@ +// Copyright 2020 BMW Group +// +// 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 { applyMiddleware, createStore } from 'redux' +import appReducer from './reducers' +import thunk from 'redux-thunk' + +export default function configureStore(initialState) { + return createStore(appReducer, initialState, applyMiddleware(thunk)) +} diff --git a/web/yarn.lock b/web/yarn.lock index 9d70c3f839..d8d20c7378 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -7305,7 +7305,7 @@ into-stream@^5.0.0: from2 "^2.3.0" p-is-promise "^3.0.0" -invariant@^2.0.0, invariant@^2.2.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" integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== @@ -12450,6 +12450,14 @@ redeyed@~2.1.0: dependencies: esprima "~4.0.0" +redux-immutable-state-invariant@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/redux-immutable-state-invariant/-/redux-immutable-state-invariant-2.1.0.tgz#308fd3cc7415a0e7f11f51ec997b6379c7055ce1" + integrity sha512-3czbDKs35FwiBRsx/3KabUk5zSOoTXC+cgVofGkpBNv3jQcqIe5JrHcF5AmVt7B/4hyJ8MijBIpCJ8cife6yJg== + dependencies: + invariant "^2.1.0" + json-stringify-safe "^5.0.1" + redux-immutable@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/redux-immutable/-/redux-immutable-3.1.0.tgz#cafbd686e0711261119b9c28960935dc47a49d0a"