From 791aed23b5192615a8c4a2bdfc354c9ef3d70e55 Mon Sep 17 00:00:00 2001 From: ivaosthu Date: Tue, 7 Aug 2018 10:33:41 +0200 Subject: [PATCH 1/7] fix(ApplicationList): icon can be null and default values will not kick in then. --- frontend/src/component/common/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/component/common/index.js b/frontend/src/component/common/index.js index 73145d6bc9..2dd8dd405d 100644 --- a/frontend/src/component/common/index.js +++ b/frontend/src/component/common/index.js @@ -11,10 +11,10 @@ export const shorten = (str, len = 50) => (str && str.length > len ? `${str.subs export const AppsLinkList = ({ apps }) => ( {apps.length > 0 && - apps.map(({ appName, description = '-', icon = 'apps' }) => ( + apps.map(({ appName, description = '-', icon }) => ( - + {appName} From 59bcabe331e771729e131ef8c0a3490672d6be5f Mon Sep 17 00:00:00 2001 From: ivaosthu Date: Mon, 6 Aug 2018 22:16:36 +0200 Subject: [PATCH 2/7] fix(router): Upgrade to react-router v. 4.x. This is rather big change to react-router and required a lot of rewrites. Mostly followed this guide: https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/guides/migrating.md --- frontend/package.json | 3 +- frontend/src/__mocks__/react-mdl.js | 3 + frontend/src/component/app.jsx | 196 +++----------- .../application/application-edit-component.js | 2 +- frontend/src/component/common/index.js | 2 +- .../feature-list-item-component-test.jsx.snap | 2 +- .../feature-list-item-component-test.jsx | 19 +- .../feature/feature-list-item-component.jsx | 2 +- .../form/form-add-feature-container.jsx | 5 +- .../form/form-update-feature-container.jsx | 5 +- .../form/form-view-feature-container.jsx | 3 +- .../feature/form/strategy-configure.jsx | 2 +- .../src/component/feature/list-component.jsx | 5 +- .../component/feature/metric-component.jsx | 2 +- .../src/component/feature/view-component.jsx | 9 +- .../src/component/menu/__tests__/.eslintrc | 5 + .../__snapshots__/breadcrumb-test.jsx.snap | 49 ++++ .../__snapshots__/drawer-test.jsx.snap | 253 ++++++++++++++++++ .../__snapshots__/footer-test.jsx.snap | 177 ++++++++++++ .../__snapshots__/routes-test.jsx.snap | 123 +++++++++ .../menu/__tests__/breadcrumb-test.jsx | 37 +++ .../component/menu/__tests__/drawer-test.jsx | 27 ++ .../component/menu/__tests__/footer-test.jsx | 27 ++ .../component/menu/__tests__/routes-test.jsx | 16 ++ frontend/src/component/menu/breadcrumb.jsx | 69 +++++ frontend/src/component/menu/drawer.jsx | 38 +++ frontend/src/component/menu/footer.jsx | 26 ++ frontend/src/component/menu/routes.js | 47 ++++ frontend/src/component/scroll-to-top.jsx | 21 ++ .../component/strategies/edit-container.js | 3 +- .../component/strategies/list-component.jsx | 2 +- .../strategies/strategy-details-component.jsx | 4 +- .../user/authentication-simple-component.jsx | 4 +- frontend/src/index.jsx | 63 +---- frontend/src/page/applications/view.js | 4 +- frontend/src/page/archive/index.js | 4 +- frontend/src/page/archive/show.js | 13 +- frontend/src/page/features/show.js | 7 +- frontend/src/page/history/toggle.js | 4 +- frontend/src/page/strategies/show.js | 6 +- frontend/yarn.lock | 108 ++++---- 41 files changed, 1068 insertions(+), 329 deletions(-) create mode 100644 frontend/src/component/menu/__tests__/.eslintrc create mode 100644 frontend/src/component/menu/__tests__/__snapshots__/breadcrumb-test.jsx.snap create mode 100644 frontend/src/component/menu/__tests__/__snapshots__/drawer-test.jsx.snap create mode 100644 frontend/src/component/menu/__tests__/__snapshots__/footer-test.jsx.snap create mode 100644 frontend/src/component/menu/__tests__/__snapshots__/routes-test.jsx.snap create mode 100644 frontend/src/component/menu/__tests__/breadcrumb-test.jsx create mode 100644 frontend/src/component/menu/__tests__/drawer-test.jsx create mode 100644 frontend/src/component/menu/__tests__/footer-test.jsx create mode 100644 frontend/src/component/menu/__tests__/routes-test.jsx create mode 100644 frontend/src/component/menu/breadcrumb.jsx create mode 100644 frontend/src/component/menu/drawer.jsx create mode 100644 frontend/src/component/menu/footer.jsx create mode 100644 frontend/src/component/menu/routes.js create mode 100644 frontend/src/component/scroll-to-top.jsx diff --git a/frontend/package.json b/frontend/package.json index c31ef3c45e..01f565defd 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -50,8 +50,7 @@ "react-mdl": "^1.11.0", "react-modal": "^3.1.13", "react-redux": "^5.0.6", - "react-router": "^3.0.0", - "react-router-scroll": "^0.4.1", + "react-router-dom": "^4.3.1", "redux": "^3.6.0", "redux-thunk": "^2.1.0", "whatwg-fetch": "^2.0.0" diff --git a/frontend/src/__mocks__/react-mdl.js b/frontend/src/__mocks__/react-mdl.js index c1ddf6d0c8..7052e6d945 100644 --- a/frontend/src/__mocks__/react-mdl.js +++ b/frontend/src/__mocks__/react-mdl.js @@ -5,6 +5,7 @@ module.exports = { CardText: 'react-mdl-CardText', CardMenu: 'react-mdl-CardMenu', DataTable: 'react-mdl-DataTable', + Drawer: 'react-mdl-Drawer', Cell: 'react-mdl-Cell', Chip: 'react-mdl-Chip', Grid: 'react-mdl-Grid', @@ -16,12 +17,14 @@ module.exports = { ListItemAction: 'react-mdl-ListItemAction', Menu: 'react-mdl-Menu', MenuItem: 'react-mdl-MenuItem', + Navigation: 'react-mdl-Navigation', ProgressBar: 'react-mdl-ProgressBar', Switch: 'react-mdl-Switch', Tab: 'react-mdl-Tab', Tabs: 'react-mdl-Tabs', TableHeader: 'react-mdl-TableHeader', Textfield: 'react-mdl-Textfield', + FooterDropDownSection: 'react-mdl-FooterDropDownSection', FooterSection: 'react-mdl-FooterSection', FooterLinkList: 'react-mdl-FooterLinkList', }; diff --git a/frontend/src/component/app.jsx b/frontend/src/component/app.jsx index 4fe0b65bc0..863f27816d 100644 --- a/frontend/src/component/app.jsx +++ b/frontend/src/component/app.jsx @@ -1,43 +1,23 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { - Layout, - Drawer, - Header, - Navigation, - Content, - Footer, - FooterSection, - FooterDropDownSection, - FooterLinkList, - Grid, - Cell, - Icon, -} from 'react-mdl'; -import { Link } from 'react-router'; +import { Layout, Header, Navigation, Content, Footer, Grid, Cell } from 'react-mdl'; +import { Route, Redirect, Switch } from 'react-router-dom'; import styles from './styles.scss'; import ErrorContainer from './error/error-container'; import AuthenticationContainer from './user/authentication-container'; import ShowUserContainer from './user/show-user-container'; import ShowApiDetailsContainer from './api/show-api-details-container'; -import { ScrollContainer } from 'react-router-scroll'; - -function replace(input, params) { - if (!params) { - return input; - } - Object.keys(params).forEach(key => { - input = input.replace(`:${key}`, params[key]); - }); - return input; -} +import Features from '../page/features'; +import { DrawerMenu } from './menu/drawer'; +import { FooterMenu } from './menu/footer'; +import Breadcrum from './menu/breadcrumb'; +import { routes } from './menu/routes'; export default class App extends Component { static propTypes = { location: PropTypes.object.isRequired, - params: PropTypes.object.isRequired, - routes: PropTypes.array.isRequired, + match: PropTypes.object.isRequired, }; static contextTypes = { @@ -58,152 +38,38 @@ export default class App extends Component { } } - getSections() { - const { routes, params } = this.props; - const unique = {}; - const result = routes - .splice(1) - .map(routeEntry => ({ - name: replace(routeEntry.pageTitle, params), - link: replace(routeEntry.link || routeEntry.path, params), - })) - .filter(entry => { - if (!unique[entry.link]) { - unique[entry.link] = true; - return true; - } - return false; - }); - - // mutate document.title: - document.title = result - .map(e => e.name) - .reverse() - .concat('Unleash') - .join(' – '); - - return result; - } - - getTitleWithLinks() { - const result = this.getSections(); - return ( - - {result.map((entry, index) => ( - 0 ? 'mdl-layout--large-screen-only' : ''}> - {index > 0 ? ' › ' : null} - - {entry.name} - - - ))} - - ); - } - render() { - const shouldUpdateScroll = (prevRouterProps, { location }) => { - if (prevRouterProps && location.pathname !== prevRouterProps.location.pathname) { - return location.action === 'POP'; - } else { - return [0, 0]; - } - }; - const createListItem = (path, caption, icon, isDrawerNavigation = false) => { - const linkColor = - isDrawerNavigation && this.context.router.isActive(path) - ? 'mdl-color-text--black' - : 'mdl-color-text--grey-900'; - const iconColor = - isDrawerNavigation && this.context.router.isActive(path) - ? 'mdl-color-text--black' - : 'mdl-color-text--grey-600'; - const renderIcon = ( - - ); - return ( - - {icon && renderIcon} - {caption} - - ); - }; - return (
-
+
}>
- - - - Unleash - -
- - {createListItem('/features', 'Feature Toggles', 'list', true)} - {createListItem('/strategies', 'Strategies', 'extension', true)} - {createListItem('/history', 'Event History', 'history', true)} - {createListItem('/archive', 'Archived Toggles', 'archive', true)} - {createListItem('/applications', 'Applications', 'apps', true)} - {createListItem('logout', 'Sign out', 'exit_to_app', true)} - -
- - - GitHub - - -
- - - - - {this.props.children} - - - -
- - - - {createListItem('/features', 'Feature Toggles', '')} - {createListItem('/strategies', 'Strategies', '')} - {createListItem('/history', 'Event History', '')} - {createListItem('/archive', 'Archived Toggles', '')} - {createListItem('/applications', 'Applications', '')} - {createListItem('/logout', 'Sign out', '')} - - - - - Node.js - Java - Go - - - - -
-
-
+ + + + + + } + /> + {routes.map(route => ( + + ))} + + + + +
+ + +
+
); diff --git a/frontend/src/component/application/application-edit-component.js b/frontend/src/component/application/application-edit-component.js index 2f8e0cfaa1..5dcc164875 100644 --- a/frontend/src/component/application/application-edit-component.js +++ b/frontend/src/component/application/application-edit-component.js @@ -2,7 +2,7 @@ import React, { Component, PureComponent } from 'react'; import PropTypes from 'prop-types'; -import { Link } from 'react-router'; +import { Link } from 'react-router-dom'; import { Grid, Cell, diff --git a/frontend/src/component/common/index.js b/frontend/src/component/common/index.js index 2dd8dd405d..6d21dd885c 100644 --- a/frontend/src/component/common/index.js +++ b/frontend/src/component/common/index.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { Link } from 'react-router'; +import { Link } from 'react-router-dom'; import { List, ListItem, ListItemContent, Button, Icon, Switch, MenuItem } from 'react-mdl'; import styles from './common.scss'; diff --git a/frontend/src/component/feature/__tests__/__snapshots__/feature-list-item-component-test.jsx.snap b/frontend/src/component/feature/__tests__/__snapshots__/feature-list-item-component-test.jsx.snap index 96acaef791..07294b161d 100644 --- a/frontend/src/component/feature/__tests__/__snapshots__/feature-list-item-component-test.jsx.snap +++ b/frontend/src/component/feature/__tests__/__snapshots__/feature-list-item-component-test.jsx.snap @@ -32,8 +32,8 @@ exports[`renders correctly with one feature 1`] = ` > Another { const featureMetrics = { lastHour: {}, lastMinute: {}, seenApps: {} }; const settings = { sort: 'name' }; const tree = renderer.create( - + + + ); expect(tree).toMatchSnapshot(); diff --git a/frontend/src/component/feature/feature-list-item-component.jsx b/frontend/src/component/feature/feature-list-item-component.jsx index 4db409569e..9ec3d019b5 100644 --- a/frontend/src/component/feature/feature-list-item-component.jsx +++ b/frontend/src/component/feature/feature-list-item-component.jsx @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { Link } from 'react-router'; +import { Link } from 'react-router-dom'; import { Switch, Chip, ListItem, ListItemAction, Icon } from 'react-mdl'; import Progress from './progress'; import { calc, styles as commonStyles } from '../common'; diff --git a/frontend/src/component/feature/form/form-add-feature-container.jsx b/frontend/src/component/feature/form/form-add-feature-container.jsx index 8361e1f0ea..28eab6bfb5 100644 --- a/frontend/src/component/feature/form/form-add-feature-container.jsx +++ b/frontend/src/component/feature/form/form-add-feature-container.jsx @@ -1,5 +1,4 @@ import { connect } from 'react-redux'; -import { hashHistory } from 'react-router'; import { createFeatureToggles, validateName } from './../../../store/feature-actions'; import { createMapper, createActions } from './../../input-helpers'; import AddFeatureComponent from './form-add-feature-component'; @@ -31,13 +30,13 @@ const prepare = (methods, dispatch) => { createFeatureToggles(input)(dispatch) .then(() => methods.clear()) - .then(() => hashHistory.push(`/features/strategies/${input.name}`)); + .then(() => this.props.history.push(`/features/strategies/${input.name}`)); }; methods.onCancel = evt => { evt.preventDefault(); methods.clear(); - hashHistory.push('/features'); + this.props.history.push('/features'); }; methods.addStrategy = v => { diff --git a/frontend/src/component/feature/form/form-update-feature-container.jsx b/frontend/src/component/feature/form/form-update-feature-container.jsx index 8366db1794..0e0971cca7 100644 --- a/frontend/src/component/feature/form/form-update-feature-container.jsx +++ b/frontend/src/component/feature/form/form-update-feature-container.jsx @@ -1,5 +1,4 @@ import { connect } from 'react-redux'; -import { hashHistory } from 'react-router'; import { requestUpdateFeatureToggle } from '../../../store/feature-actions'; import { createMapper, createActions } from '../../input-helpers'; @@ -42,13 +41,13 @@ const prepare = (methods, dispatch) => { // TODO: should add error handling requestUpdateFeatureToggle(input)(dispatch) .then(() => methods.clear()) - .then(() => hashHistory.push(`/features`)); + .then(() => this.props.history.push(`/features`)); }; methods.onCancel = evt => { evt.preventDefault(); methods.clear(); - hashHistory.push(`/features`); + this.props.history.push(`/features`); }; methods.addStrategy = v => { diff --git a/frontend/src/component/feature/form/form-view-feature-container.jsx b/frontend/src/component/feature/form/form-view-feature-container.jsx index 3ed8e9605b..1f7dc4c463 100644 --- a/frontend/src/component/feature/form/form-view-feature-container.jsx +++ b/frontend/src/component/feature/form/form-view-feature-container.jsx @@ -1,7 +1,6 @@ import { connect } from 'react-redux'; import { createMapper, createActions } from '../../input-helpers'; import ViewFeatureToggleComponent from './form-view-feature-component'; -import { hashHistory } from 'react-router'; const ID = 'view-feature-toggle'; function getId(props) { @@ -27,7 +26,7 @@ const prepare = methods => { methods.onCancel = evt => { evt.preventDefault(); methods.clear(); - hashHistory.push(`/archive`); + this.props.history.push(`/archive`); }; return methods; }; diff --git a/frontend/src/component/feature/form/strategy-configure.jsx b/frontend/src/component/feature/form/strategy-configure.jsx index da816b4ac4..b18e5e850a 100644 --- a/frontend/src/component/feature/form/strategy-configure.jsx +++ b/frontend/src/component/feature/form/strategy-configure.jsx @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Textfield, Button, Card, CardTitle, CardText, CardActions, CardMenu, IconButton, Icon } from 'react-mdl'; import { DragSource, DropTarget } from 'react-dnd'; -import { Link } from 'react-router'; +import { Link } from 'react-router-dom'; import StrategyInputPercentage from './strategy-input-percentage'; import StrategyInputList from './strategy-input-list'; import styles from './strategy.scss'; diff --git a/frontend/src/component/feature/list-component.jsx b/frontend/src/component/feature/list-component.jsx index baf7777548..5e1087e50c 100644 --- a/frontend/src/component/feature/list-component.jsx +++ b/frontend/src/component/feature/list-component.jsx @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import Feature from './feature-list-item-component'; -import { hashHistory, Link } from 'react-router'; +import { Link } from 'react-router-dom'; import { Icon, FABButton, Textfield, Menu, MenuItem, Card, CardActions, List } from 'react-mdl'; import { MenuItemWithIcon, DropdownButton, styles as commonStyles } from '../common'; import styles from './feature.scss'; @@ -18,6 +18,7 @@ export default class FeatureListComponent extends React.Component { updateSetting: PropTypes.func.isRequired, toggleFeature: PropTypes.func, settings: PropTypes.object, + history: PropTypes.object.isRequired, }; static contextTypes = { @@ -27,7 +28,7 @@ export default class FeatureListComponent extends React.Component { componentDidMount() { if (this.props.logout) { this.props.logoutUser(); - hashHistory.push(`/`); + this.props.history.push(`/`); } if (this.props.fetchFeatureToggles) { this.props.fetchFeatureToggles(); diff --git a/frontend/src/component/feature/metric-component.jsx b/frontend/src/component/feature/metric-component.jsx index 33bd4fa947..44bd254b87 100644 --- a/frontend/src/component/feature/metric-component.jsx +++ b/frontend/src/component/feature/metric-component.jsx @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Grid, Cell, Icon, Chip, ChipContact } from 'react-mdl'; import Progress from './progress'; -import { Link } from 'react-router'; +import { Link } from 'react-router-dom'; import { AppsLinkList, calc } from '../common'; import { formatFullDateTimeWithLocale } from '../common/util'; import styles from './metrics.scss'; diff --git a/frontend/src/component/feature/view-component.jsx b/frontend/src/component/feature/view-component.jsx index e76954bf29..30ad2a84a9 100644 --- a/frontend/src/component/feature/view-component.jsx +++ b/frontend/src/component/feature/view-component.jsx @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Tabs, Tab, ProgressBar, Button, Card, CardText, CardTitle, CardActions, Textfield, Switch } from 'react-mdl'; -import { hashHistory, Link } from 'react-router'; +import { Link } from 'react-router-dom'; import HistoryComponent from '../history/history-list-toggle-container'; import MetricComponent from './metric-container'; @@ -33,6 +33,7 @@ export default class ViewFeatureToggleComponent extends React.Component { fetchFeatureToggles: PropTypes.func, editFeatureToggle: PropTypes.func, featureToggle: PropTypes.object, + history: PropTypes.object.isRequired, }; componentWillMount() { @@ -62,7 +63,7 @@ export default class ViewFeatureToggleComponent extends React.Component { goToTab(tabName, featureToggleName) { let view = this.props.fetchFeatureToggles ? 'features' : 'archive'; - hashHistory.push(`/${view}/${tabName}/${featureToggleName}`); + this.props.history.push(`/${view}/${tabName}/${featureToggleName}`); } render() { @@ -105,12 +106,12 @@ export default class ViewFeatureToggleComponent extends React.Component { window.confirm('Are you sure you want to remove this toggle?') ) { removeFeatureToggle(featureToggle.name); - hashHistory.push('/features'); + this.props.history.push('/features'); } }; const reviveToggle = () => { revive(featureToggle.name); - hashHistory.push('/features'); + this.props.history.push('/features'); }; const updateFeatureToggle = () => { let feature = { ...featureToggle }; diff --git a/frontend/src/component/menu/__tests__/.eslintrc b/frontend/src/component/menu/__tests__/.eslintrc new file mode 100644 index 0000000000..eba2077219 --- /dev/null +++ b/frontend/src/component/menu/__tests__/.eslintrc @@ -0,0 +1,5 @@ +{ + "env": { + "jest": true + } +} diff --git a/frontend/src/component/menu/__tests__/__snapshots__/breadcrumb-test.jsx.snap b/frontend/src/component/menu/__tests__/__snapshots__/breadcrumb-test.jsx.snap new file mode 100644 index 0000000000..ee64a666c4 --- /dev/null +++ b/frontend/src/component/menu/__tests__/__snapshots__/breadcrumb-test.jsx.snap @@ -0,0 +1,49 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`breadcrumb for /features 1`] = ` + + + Feature Toggles + + +`; + +exports[`breadcrumb for /features/view/Demo 1`] = ` + + + Feature Toggles + + + + › + + + Demo + + +
+`; + +exports[`breadcrumb for /strategies 1`] = ` + + + Strategies + + +`; diff --git a/frontend/src/component/menu/__tests__/__snapshots__/drawer-test.jsx.snap b/frontend/src/component/menu/__tests__/__snapshots__/drawer-test.jsx.snap new file mode 100644 index 0000000000..ab4ba96f3b --- /dev/null +++ b/frontend/src/component/menu/__tests__/__snapshots__/drawer-test.jsx.snap @@ -0,0 +1,253 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render DrawerMenu 1`] = ` + + + + + Unleash + + +
+ + + + + Feature Toggles + + + + + Strategies + + + + + Event History + + + + + Archived Toggles + + + + + Applications + + + + + Sign out + + +
+ + + + GitHub + + +
+`; + +exports[`should render DrawerMenu with "features" selected 1`] = ` + + + + + Unleash + + +
+ + + + + Feature Toggles + + + + + Strategies + + + + + Event History + + + + + Archived Toggles + + + + + Applications + + + + + Sign out + + +
+ + + + GitHub + + +
+`; diff --git a/frontend/src/component/menu/__tests__/__snapshots__/footer-test.jsx.snap b/frontend/src/component/menu/__tests__/__snapshots__/footer-test.jsx.snap new file mode 100644 index 0000000000..1fc175925b --- /dev/null +++ b/frontend/src/component/menu/__tests__/__snapshots__/footer-test.jsx.snap @@ -0,0 +1,177 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render DrawerMenu 1`] = ` + + + + + Feature Toggles + + + Strategies + + + Event History + + + Archived Toggles + + + Applications + + + Sign out + + + + + + + Node.js + + + Java + + + Go + + + + +`; + +exports[`should render DrawerMenu with "features" selected 1`] = ` + + + + + Feature Toggles + + + Strategies + + + Event History + + + Archived Toggles + + + Applications + + + Sign out + + + + + + + Node.js + + + Java + + + Go + + + + +`; diff --git a/frontend/src/component/menu/__tests__/__snapshots__/routes-test.jsx.snap b/frontend/src/component/menu/__tests__/__snapshots__/routes-test.jsx.snap new file mode 100644 index 0000000000..cf94282c9c --- /dev/null +++ b/frontend/src/component/menu/__tests__/__snapshots__/routes-test.jsx.snap @@ -0,0 +1,123 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`returns all baseRoutes 1`] = ` +Array [ + Object { + "component": [Function], + "icon": "list", + "path": "/features", + "title": "Feature Toggles", + }, + Object { + "component": [Function], + "icon": "extension", + "path": "/strategies", + "title": "Strategies", + }, + Object { + "component": [Function], + "icon": "history", + "path": "/history", + "title": "Event History", + }, + Object { + "component": [Function], + "icon": "archive", + "path": "/archive", + "title": "Archived Toggles", + }, + Object { + "component": [Function], + "icon": "apps", + "path": "/applications", + "title": "Applications", + }, + Object { + "icon": "exit_to_app", + "path": "logout", + "title": "Sign out", + }, +] +`; + +exports[`returns all defined routes 1`] = ` +Array [ + Object { + "component": [Function], + "parent": "/features", + "path": "/features/create", + "title": "Create", + }, + Object { + "component": [Function], + "parent": "/features", + "path": "/features/:activeTab/:name", + "title": ":name", + }, + Object { + "component": [Function], + "icon": "list", + "path": "/features", + "title": "Feature Toggles", + }, + Object { + "component": [Function], + "parent": "/strategies", + "path": "/strategies/create", + "title": "Create", + }, + Object { + "component": [Function], + "parent": "/strategies", + "path": "/strategies/:activeTab/:strategyName", + "title": ":strategyName", + }, + Object { + "component": [Function], + "icon": "extension", + "path": "/strategies", + "title": "Strategies", + }, + Object { + "component": [Function], + "parent": "/history", + "path": "/history/:toggleName", + "title": ":toggleName", + }, + Object { + "component": [Function], + "icon": "history", + "path": "/history", + "title": "Event History", + }, + Object { + "component": [Function], + "parent": "/archive", + "path": "/archive/:activeTab/:name", + "title": ":name", + }, + Object { + "component": [Function], + "icon": "archive", + "path": "/archive", + "title": "Archived Toggles", + }, + Object { + "component": [Function], + "parent": "/applications", + "path": "/applications/:name", + "title": ":name", + }, + Object { + "component": [Function], + "icon": "apps", + "path": "/applications", + "title": "Applications", + }, + Object { + "icon": "exit_to_app", + "path": "logout", + "title": "Sign out", + }, +] +`; diff --git a/frontend/src/component/menu/__tests__/breadcrumb-test.jsx b/frontend/src/component/menu/__tests__/breadcrumb-test.jsx new file mode 100644 index 0000000000..cb0e74b176 --- /dev/null +++ b/frontend/src/component/menu/__tests__/breadcrumb-test.jsx @@ -0,0 +1,37 @@ +import React from 'react'; +import renderer from 'react-test-renderer'; +import { MemoryRouter } from 'react-router-dom'; + +import Breadcrumb from '../breadcrumb'; + +jest.mock('react-mdl'); + +test('breadcrumb for /features', () => { + const tree = renderer.create( + + + + ); + + expect(tree).toMatchSnapshot(); +}); + +test('breadcrumb for /features/view/Demo', () => { + const tree = renderer.create( + + + + ); + + expect(tree).toMatchSnapshot(); +}); + +test('breadcrumb for /strategies', () => { + const tree = renderer.create( + + + + ); + + expect(tree).toMatchSnapshot(); +}); diff --git a/frontend/src/component/menu/__tests__/drawer-test.jsx b/frontend/src/component/menu/__tests__/drawer-test.jsx new file mode 100644 index 0000000000..f581522202 --- /dev/null +++ b/frontend/src/component/menu/__tests__/drawer-test.jsx @@ -0,0 +1,27 @@ +import React from 'react'; +import renderer from 'react-test-renderer'; +import { MemoryRouter } from 'react-router-dom'; + +import { DrawerMenu } from '../drawer'; + +jest.mock('react-mdl'); + +test('should render DrawerMenu', () => { + const tree = renderer.create( + + + + ); + + expect(tree).toMatchSnapshot(); +}); + +test('should render DrawerMenu with "features" selected', () => { + const tree = renderer.create( + + + + ); + + expect(tree).toMatchSnapshot(); +}); diff --git a/frontend/src/component/menu/__tests__/footer-test.jsx b/frontend/src/component/menu/__tests__/footer-test.jsx new file mode 100644 index 0000000000..6993c42fc5 --- /dev/null +++ b/frontend/src/component/menu/__tests__/footer-test.jsx @@ -0,0 +1,27 @@ +import React from 'react'; +import renderer from 'react-test-renderer'; +import { MemoryRouter } from 'react-router-dom'; + +import { FooterMenu } from '../footer'; + +jest.mock('react-mdl'); + +test('should render DrawerMenu', () => { + const tree = renderer.create( + + + + ); + + expect(tree).toMatchSnapshot(); +}); + +test('should render DrawerMenu with "features" selected', () => { + const tree = renderer.create( + + + + ); + + expect(tree).toMatchSnapshot(); +}); diff --git a/frontend/src/component/menu/__tests__/routes-test.jsx b/frontend/src/component/menu/__tests__/routes-test.jsx new file mode 100644 index 0000000000..20ce8e87a2 --- /dev/null +++ b/frontend/src/component/menu/__tests__/routes-test.jsx @@ -0,0 +1,16 @@ +import { routes, baseRoutes, getRoute } from '../routes'; + +test('returns all defined routes', () => { + expect(routes.length).toEqual(13); + expect(routes).toMatchSnapshot(); +}); + +test('returns all baseRoutes', () => { + expect(baseRoutes.length).toEqual(6); + expect(baseRoutes).toMatchSnapshot(); +}); + +test('getRoute() returns named route', () => { + const featuresRoute = getRoute('/features'); + expect(featuresRoute.path).toEqual('/features'); +}); diff --git a/frontend/src/component/menu/breadcrumb.jsx b/frontend/src/component/menu/breadcrumb.jsx new file mode 100644 index 0000000000..ec02bb6ef8 --- /dev/null +++ b/frontend/src/component/menu/breadcrumb.jsx @@ -0,0 +1,69 @@ +import React from 'react'; +import { Link, Route, Switch } from 'react-router-dom'; + +import { routes, getRoute } from './routes'; + +import styles from '../styles.scss'; + +const renderDoubleBread = (currentTitle, parentRoute) => { + document.title = `${currentTitle} - ${parentRoute.title} - Unleash`; + return ( + + + {parentRoute.title} + + + + + {currentTitle} + + + + ); +}; + +const renderBread = route => { + document.title = `${route.title} - Unleash`; + return ( + + + {route.title} + + + ); +}; + +const renderRoute = (params, route) => { + if (!route) { + return null; + } + const title = route.title.startsWith(':') ? params[route.title.substring(1)] : route.title; + return route.parent ? renderDoubleBread(title, getRoute(route.parent)) : renderBread(route); +}; + +/* + Render the breadcrumb. + + We only support two levels. + + Examples: + - Features + - Features > Create + - Features > SomeToggle + */ +const Breadcrumb = () => ( + + {routes.map(route => ( + renderRoute(params, route)} + /> + ))} + +); + +export default Breadcrumb; diff --git a/frontend/src/component/menu/drawer.jsx b/frontend/src/component/menu/drawer.jsx new file mode 100644 index 0000000000..ac30a33a49 --- /dev/null +++ b/frontend/src/component/menu/drawer.jsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { Drawer, Icon, Navigation } from 'react-mdl'; +import { NavLink } from 'react-router-dom'; +import styles from '../styles.scss'; + +import { baseRoutes as routes } from './routes'; + +export const DrawerMenu = () => ( + + + + Unleash + +
+ + {routes.map(item => ( + + {item.title} + + ))} + +
+ + + GitHub + + +
+); diff --git a/frontend/src/component/menu/footer.jsx b/frontend/src/component/menu/footer.jsx new file mode 100644 index 0000000000..1f8713745a --- /dev/null +++ b/frontend/src/component/menu/footer.jsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { FooterDropDownSection, FooterLinkList, FooterSection } from 'react-mdl'; +import { NavLink } from 'react-router-dom'; + +import { baseRoutes as routes } from './routes'; + +export const FooterMenu = () => ( + + + + {routes.map(item => ( + + {item.title} + + ))} + + + + + Node.js + Java + Go + + + +); diff --git a/frontend/src/component/menu/routes.js b/frontend/src/component/menu/routes.js new file mode 100644 index 0000000000..1a5acac0d5 --- /dev/null +++ b/frontend/src/component/menu/routes.js @@ -0,0 +1,47 @@ +import CreateFeatureToggle from '../../page/features/create'; +import ViewFeatureToggle from '../../page/features/show'; +import Features from '../../page/features'; +import CreateStrategies from '../../page/strategies/create'; +import StrategyView from '../../page/strategies/show'; +import Strategies from '../../page/strategies'; +import HistoryPage from '../../page/history'; +import HistoryTogglePage from '../../page/history/toggle'; +import ShowArchive from '../../page/archive/show'; +import Archive from '../../page/archive'; +import Applications from '../../page/applications'; +import ApplicationView from '../../page/applications/view'; + +export const routes = [ + // Features + { path: '/features/create', parent: '/features', title: 'Create', component: CreateFeatureToggle }, + { path: '/features/:activeTab/:name', parent: '/features', title: ':name', component: ViewFeatureToggle }, + { path: '/features', title: 'Feature Toggles', icon: 'list', component: Features }, + + // Strategies + { path: '/strategies/create', title: 'Create', parent: '/strategies', component: CreateStrategies }, + { + path: '/strategies/:activeTab/:strategyName', + title: ':strategyName', + parent: '/strategies', + component: StrategyView, + }, + { path: '/strategies', title: 'Strategies', icon: 'extension', component: Strategies }, + + // History + { path: '/history/:toggleName', title: ':toggleName', parent: '/history', component: HistoryTogglePage }, + { path: '/history', title: 'Event History', icon: 'history', component: HistoryPage }, + + // Archive + { path: '/archive/:activeTab/:name', title: ':name', parent: '/archive', component: ShowArchive }, + { path: '/archive', title: 'Archived Toggles', icon: 'archive', component: Archive }, + + // Applications + { path: '/applications/:name', title: ':name', parent: '/applications', component: ApplicationView }, + { path: '/applications', title: 'Applications', icon: 'apps', component: Applications }, + + { path: 'logout', title: 'Sign out', icon: 'exit_to_app' }, +]; + +export const getRoute = path => routes.find(route => route.path === path); + +export const baseRoutes = routes.filter(route => !route.parent); diff --git a/frontend/src/component/scroll-to-top.jsx b/frontend/src/component/scroll-to-top.jsx new file mode 100644 index 0000000000..1c5f88c13e --- /dev/null +++ b/frontend/src/component/scroll-to-top.jsx @@ -0,0 +1,21 @@ +import { Component } from 'react'; +import { withRouter } from 'react-router-dom'; +import PropTypes from 'prop-types'; + +class ScrollToTop extends Component { + static propTypes = { + location: PropTypes.object.isRequired, + }; + + componentDidUpdate(prevProps) { + if (this.props.location !== prevProps.location) { + window.scrollTo(0, 0); + } + } + + render() { + return this.props.children; + } +} + +export default withRouter(ScrollToTop); diff --git a/frontend/src/component/strategies/edit-container.js b/frontend/src/component/strategies/edit-container.js index 52ea626ed5..bf13af98ed 100644 --- a/frontend/src/component/strategies/edit-container.js +++ b/frontend/src/component/strategies/edit-container.js @@ -1,5 +1,4 @@ import { connect } from 'react-redux'; -import { hashHistory } from 'react-router'; import { createMapper, createActions } from '../input-helpers'; import { updateStrategy } from '../../store/strategy/actions'; @@ -41,7 +40,7 @@ const prepare = (methods, dispatch) => { parameters, })(dispatch) .then(() => methods.clear()) - .then(() => hashHistory.push(`/strategies/view/${input.name}`)); + .then(() => this.props.history.push(`/strategies/view/${input.name}`)); }; methods.onCancel = e => { diff --git a/frontend/src/component/strategies/list-component.jsx b/frontend/src/component/strategies/list-component.jsx index 020602a706..a48ef734cf 100644 --- a/frontend/src/component/strategies/list-component.jsx +++ b/frontend/src/component/strategies/list-component.jsx @@ -1,6 +1,6 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { Link } from 'react-router'; +import { Link } from 'react-router-dom'; import { List, ListItem, ListItemContent, IconButton, Grid, Cell } from 'react-mdl'; import { HeaderTitle } from '../common'; diff --git a/frontend/src/component/strategies/strategy-details-component.jsx b/frontend/src/component/strategies/strategy-details-component.jsx index c9762b8e05..9c612cdfb8 100644 --- a/frontend/src/component/strategies/strategy-details-component.jsx +++ b/frontend/src/component/strategies/strategy-details-component.jsx @@ -1,6 +1,5 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { hashHistory } from 'react-router'; import { Tabs, Tab, ProgressBar, Grid, Cell } from 'react-mdl'; import ShowStrategy from './show-strategy-component'; import EditStrategy from './edit-container'; @@ -21,6 +20,7 @@ export default class StrategyDetails extends Component { fetchStrategies: PropTypes.func.isRequired, fetchApplications: PropTypes.func.isRequired, fetchFeatureToggles: PropTypes.func.isRequired, + history: PropTypes.object.isRequired, }; componentDidMount() { @@ -50,7 +50,7 @@ export default class StrategyDetails extends Component { } goToTab(tabName) { - hashHistory.push(`/strategies/${tabName}/${this.props.strategyName}`); + this.props.history.push(`/strategies/${tabName}/${this.props.strategyName}`); } render() { diff --git a/frontend/src/component/user/authentication-simple-component.jsx b/frontend/src/component/user/authentication-simple-component.jsx index 4f3fb51555..ad1dbd4d04 100644 --- a/frontend/src/component/user/authentication-simple-component.jsx +++ b/frontend/src/component/user/authentication-simple-component.jsx @@ -1,6 +1,5 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { hashHistory } from 'react-router'; import { CardActions, Button, Textfield } from 'react-mdl'; class SimpleAuthenticationComponent extends React.Component { @@ -8,6 +7,7 @@ class SimpleAuthenticationComponent extends React.Component { authDetails: PropTypes.object.isRequired, unsecureLogin: PropTypes.func.isRequired, fetchFeatureToggles: PropTypes.func.isRequired, + history: PropTypes.object.isRequired, }; handleSubmit = evt => { @@ -20,7 +20,7 @@ class SimpleAuthenticationComponent extends React.Component { .unsecureLogin(path, user) .then(this.props.fetchFeatureToggles) .then(() => { - hashHistory.push('/features'); + this.props.history.push('/features'); }); }; diff --git a/frontend/src/index.jsx b/frontend/src/index.jsx index 188f331cce..33104de9b9 100644 --- a/frontend/src/index.jsx +++ b/frontend/src/index.jsx @@ -4,8 +4,7 @@ import 'react-mdl/extra/material.js'; import React from 'react'; import ReactDOM from 'react-dom'; -import { applyRouterMiddleware, Router, Route, IndexRedirect, hashHistory } from 'react-router'; -import { useScroll } from 'react-router-scroll'; +import { HashRouter, Route } from 'react-router-dom'; import { Provider } from 'react-redux'; import thunkMiddleware from 'redux-thunk'; import { createStore, applyMiddleware, compose } from 'redux'; @@ -13,20 +12,7 @@ import { createStore, applyMiddleware, compose } from 'redux'; import store from './store'; import MetricsPoller from './metrics-poller'; import App from './component/app'; - -import Features from './page/features'; -import CreateFeatureToggle from './page/features/create'; -import ViewFeatureToggle from './page/features/show'; -import Strategies from './page/strategies'; -import StrategyView from './page/strategies/show'; -import CreateStrategies from './page/strategies/create'; -import HistoryPage from './page/history'; -import HistoryTogglePage from './page/history/toggle'; -import Archive from './page/archive'; -import ShowArchive from './page/archive/show'; -import Applications from './page/applications'; -import ApplicationView from './page/applications/view'; -import LogoutFeatures from './page/user/logout'; +import ScrollToTop from './component/scroll-to-top'; let composeEnhancers; @@ -40,48 +26,13 @@ const unleashStore = createStore(store, composeEnhancers(applyMiddleware(thunkMi const metricsPoller = new MetricsPoller(unleashStore); metricsPoller.start(); -// "pageTitle" and "link" attributes are for internal usage only - ReactDOM.render( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + , document.getElementById('app') ); diff --git a/frontend/src/page/applications/view.js b/frontend/src/page/applications/view.js index 1838eb5abc..13e72c32a6 100644 --- a/frontend/src/page/applications/view.js +++ b/frontend/src/page/applications/view.js @@ -2,10 +2,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import ApplicationEditComponent from '../../component/application/application-edit-container'; -const render = ({ params }) => ; +const render = ({ match: { params } }) => ; render.propTypes = { - params: PropTypes.object.isRequired, + match: PropTypes.object.isRequired, }; export default render; diff --git a/frontend/src/page/archive/index.js b/frontend/src/page/archive/index.js index bbd37e3600..9c5cfba1df 100644 --- a/frontend/src/page/archive/index.js +++ b/frontend/src/page/archive/index.js @@ -2,9 +2,9 @@ import React from 'react'; import Archive from '../../component/archive/archive-list-container'; import PropTypes from 'prop-types'; -const render = ({ params }) => ; +const render = ({ match }) => ; render.propTypes = { - params: PropTypes.object, + match: PropTypes.object, }; export default render; diff --git a/frontend/src/page/archive/show.js b/frontend/src/page/archive/show.js index 674e303ac0..242a588d71 100644 --- a/frontend/src/page/archive/show.js +++ b/frontend/src/page/archive/show.js @@ -4,11 +4,18 @@ import ViewFeatureToggle from './../../component/archive/view-container'; export default class Features extends PureComponent { static propTypes = { - params: PropTypes.object.isRequired, + match: PropTypes.object.isRequired, + history: PropTypes.object.isRequired, }; render() { - const { params } = this.props; - return ; + const { match, history } = this.props; + return ( + + ); } } diff --git a/frontend/src/page/features/show.js b/frontend/src/page/features/show.js index 4143c1f289..68c4b6da63 100644 --- a/frontend/src/page/features/show.js +++ b/frontend/src/page/features/show.js @@ -4,11 +4,12 @@ import ViewFeatureToggle from './../../component/feature/view-container'; export default class Features extends PureComponent { static propTypes = { - params: PropTypes.object.isRequired, + match: PropTypes.object.isRequired, + history: PropTypes.object.isRequired, }; render() { - const { params } = this.props; - return ; + const { match: { params }, history } = this.props; + return ; } } diff --git a/frontend/src/page/history/toggle.js b/frontend/src/page/history/toggle.js index 0c464780bb..e0146459ad 100644 --- a/frontend/src/page/history/toggle.js +++ b/frontend/src/page/history/toggle.js @@ -2,10 +2,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import HistoryListToggle from '../../component/history/history-list-toggle-container'; -const render = ({ params }) => ; +const render = ({ match: { params } }) => ; render.propTypes = { - params: PropTypes.object.isRequired, + match: PropTypes.object.isRequired, }; export default render; diff --git a/frontend/src/page/strategies/show.js b/frontend/src/page/strategies/show.js index a33d5809e6..6f3125935e 100644 --- a/frontend/src/page/strategies/show.js +++ b/frontend/src/page/strategies/show.js @@ -2,10 +2,12 @@ import React from 'react'; import PropTypes from 'prop-types'; import ShowStrategy from '../../component/strategies/strategy-details-container'; -const render = ({ params }) => ; +const render = ({ match: { params } }) => ( + +); render.propTypes = { - params: PropTypes.object.isRequired, + match: PropTypes.object.isRequired, }; export default render; diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 37f81fe163..274277cb1d 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1732,14 +1732,6 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: safe-buffer "^5.0.1" sha.js "^2.4.8" -create-react-class@^15.5.1: - version "15.6.3" - resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.6.3.tgz#2d73237fb3f970ae6ebe011a9e66f46dbca80036" - dependencies: - fbjs "^0.8.9" - loose-envify "^1.3.1" - object-assign "^4.1.1" - cross-spawn@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-3.0.1.tgz#1256037ecb9f0c5f79e3d6ef135e30770184b982" @@ -2030,10 +2022,6 @@ doctrine@^2.1.0: dependencies: esutils "^2.0.2" -dom-helpers@^3.2.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.3.1.tgz#fc1a4e15ffdf60ddde03a480a9c0fece821dd4a6" - dom-serializer@0, dom-serializer@~0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82" @@ -2674,7 +2662,7 @@ fbjs@^0.8.1: setimmediate "^1.0.5" ua-parser-js "^0.7.18" -fbjs@^0.8.16, fbjs@^0.8.9: +fbjs@^0.8.16: version "0.8.16" resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db" dependencies: @@ -3188,13 +3176,14 @@ hawk@~6.0.2: hoek "4.x.x" sntp "2.x.x" -history@^3.0.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/history/-/history-3.3.0.tgz#fcedcce8f12975371545d735461033579a6dae9c" +history@^4.7.2: + version "4.7.2" + resolved "https://registry.yarnpkg.com/history/-/history-4.7.2.tgz#22b5c7f31633c5b8021c7f4a8a954ac139ee8d5b" dependencies: invariant "^2.2.1" loose-envify "^1.2.0" - query-string "^4.2.2" + resolve-pathname "^2.2.0" + value-equal "^0.4.0" warning "^3.0.0" hmac-drbg@^1.0.0: @@ -3213,10 +3202,6 @@ hoek@4.x.x: version "4.2.0" resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d" -hoist-non-react-statics@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz#aa448cf0986d55cc40773b17174b7dd066cb7cfb" - hoist-non-react-statics@^2.2.1: version "2.3.1" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.3.1.tgz#343db84c6018c650778898240135a1420ee22ce0" @@ -3699,6 +3684,10 @@ is-wsl@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -5062,6 +5051,12 @@ path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" +path-to-regexp@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d" + dependencies: + isarray "0.0.1" + path-to-regexp@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-2.1.0.tgz#7e30f9f5b134bd6a28ffc2e3ef1e47075ac5259b" @@ -5238,7 +5233,7 @@ promise@^7.1.1: dependencies: asap "~2.0.3" -prop-types@^15.5.0, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.6, prop-types@^15.5.7, prop-types@^15.6.0: +prop-types@^15.5.0, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.7, prop-types@^15.6.0: version "15.6.0" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.0.tgz#ceaf083022fc46b4a35f69e13ef75aed0d639856" dependencies: @@ -5246,7 +5241,7 @@ prop-types@^15.5.0, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.6, loose-envify "^1.3.1" object-assign "^4.1.1" -prop-types@^15.6.2: +prop-types@^15.6.1, prop-types@^15.6.2: version "15.6.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102" dependencies: @@ -5302,13 +5297,6 @@ qs@~6.4.0: version "6.4.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" -query-string@^4.2.2: - version "4.3.4" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" - dependencies: - object-assign "^4.1.0" - strict-uri-encode "^1.0.0" - querystring-es3@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" @@ -5465,25 +5453,28 @@ react-redux@^5.0.6: loose-envify "^1.1.0" prop-types "^15.5.10" -react-router-scroll@^0.4.1: - version "0.4.4" - resolved "https://registry.yarnpkg.com/react-router-scroll/-/react-router-scroll-0.4.4.tgz#4d7b71c75b45ff296e4adca1e029a86e898a155d" +react-router-dom@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.3.1.tgz#4c2619fc24c4fa87c9fd18f4fb4a43fe63fbd5c6" dependencies: - prop-types "^15.6.0" - scroll-behavior "^0.9.5" - warning "^3.0.0" + history "^4.7.2" + invariant "^2.2.4" + loose-envify "^1.3.1" + prop-types "^15.6.1" + react-router "^4.3.1" + warning "^4.0.1" -react-router@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-3.2.0.tgz#62b6279d589b70b34e265113e4c0a9261a02ed36" +react-router@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-4.3.1.tgz#aada4aef14c809cb2e686b05cee4742234506c4e" dependencies: - create-react-class "^15.5.1" - history "^3.0.0" - hoist-non-react-statics "^1.2.0" - invariant "^2.2.1" - loose-envify "^1.2.0" - prop-types "^15.5.6" - warning "^3.0.0" + history "^4.7.2" + hoist-non-react-statics "^2.5.0" + invariant "^2.2.4" + loose-envify "^1.3.1" + path-to-regexp "^1.7.0" + prop-types "^15.6.1" + warning "^4.0.1" react-test-renderer@^16.0.0-0, react-test-renderer@^16.2.0: version "16.2.0" @@ -5852,6 +5843,10 @@ resolve-from@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" +resolve-pathname@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-2.2.0.tgz#7e9ae21ed815fd63ab189adeee64dc831eefa879" + resolve-url@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" @@ -5967,13 +5962,6 @@ schema-utils@^0.4.5: ajv "^6.1.0" ajv-keywords "^3.1.0" -scroll-behavior@^0.9.5: - version "0.9.9" - resolved "https://registry.yarnpkg.com/scroll-behavior/-/scroll-behavior-0.9.9.tgz#ebfe0658455b82ad885b66195215416674dacce2" - dependencies: - dom-helpers "^3.2.1" - invariant "^2.2.2" - scss-tokenizer@^0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1" @@ -6351,10 +6339,6 @@ stream-http@^2.7.2: to-arraybuffer "^1.0.0" xtend "^4.0.0" -strict-uri-encode@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" - string-length@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed" @@ -6795,6 +6779,10 @@ validate-npm-package-license@^3.0.1: spdx-correct "~1.0.0" spdx-expression-parse "~1.0.0" +value-equal@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-0.4.0.tgz#c5bdd2f54ee093c04839d71ce2e4758a6890abc7" + vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" @@ -6831,6 +6819,12 @@ warning@^3.0.0: dependencies: loose-envify "^1.0.0" +warning@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.1.tgz#66ce376b7fbfe8a887c22bdf0e7349d73d397745" + dependencies: + loose-envify "^1.0.0" + watch@~0.18.0: version "0.18.0" resolved "https://registry.yarnpkg.com/watch/-/watch-0.18.0.tgz#28095476c6df7c90c963138990c0a5423eb4b986" From b97480c01cd6f0b7b30658237a6a3141cef0894f Mon Sep 17 00:00:00 2001 From: ivaosthu Date: Fri, 10 Aug 2018 16:07:18 +0200 Subject: [PATCH 3/7] fix(Features): Create/add feature toggle wants to change the current url. --- .../component/feature/form/form-add-feature-container.jsx | 6 +++--- .../feature/form/form-update-feature-container.jsx | 6 +++--- frontend/src/component/feature/view-component.jsx | 4 +++- frontend/src/page/features/create.js | 7 ++++++- frontend/src/page/features/index.js | 7 ++++++- 5 files changed, 21 insertions(+), 9 deletions(-) diff --git a/frontend/src/component/feature/form/form-add-feature-container.jsx b/frontend/src/component/feature/form/form-add-feature-container.jsx index 28eab6bfb5..6835ffbc52 100644 --- a/frontend/src/component/feature/form/form-add-feature-container.jsx +++ b/frontend/src/component/feature/form/form-add-feature-container.jsx @@ -16,7 +16,7 @@ const mapStateToProps = createMapper({ return { name }; }, }); -const prepare = (methods, dispatch) => { +const prepare = (methods, dispatch, ownProps) => { methods.onSubmit = input => e => { e.preventDefault(); @@ -30,13 +30,13 @@ const prepare = (methods, dispatch) => { createFeatureToggles(input)(dispatch) .then(() => methods.clear()) - .then(() => this.props.history.push(`/features/strategies/${input.name}`)); + .then(() => ownProps.history.push(`/features/strategies/${input.name}`)); }; methods.onCancel = evt => { evt.preventDefault(); methods.clear(); - this.props.history.push('/features'); + ownProps.history.push('/features'); }; methods.addStrategy = v => { diff --git a/frontend/src/component/feature/form/form-update-feature-container.jsx b/frontend/src/component/feature/form/form-update-feature-container.jsx index 0e0971cca7..d8839142b6 100644 --- a/frontend/src/component/feature/form/form-update-feature-container.jsx +++ b/frontend/src/component/feature/form/form-update-feature-container.jsx @@ -24,7 +24,7 @@ const mapStateToProps = createMapper({ }, }); -const prepare = (methods, dispatch) => { +const prepare = (methods, dispatch, ownProps) => { methods.onSubmit = (input, features) => e => { e.preventDefault(); @@ -41,13 +41,13 @@ const prepare = (methods, dispatch) => { // TODO: should add error handling requestUpdateFeatureToggle(input)(dispatch) .then(() => methods.clear()) - .then(() => this.props.history.push(`/features`)); + .then(() => ownProps.history.push(`/features`)); }; methods.onCancel = evt => { evt.preventDefault(); methods.clear(); - this.props.history.push(`/features`); + ownProps.history.push(`/features`); }; methods.addStrategy = v => { diff --git a/frontend/src/component/feature/view-component.jsx b/frontend/src/component/feature/view-component.jsx index 30ad2a84a9..16c2cbf3db 100644 --- a/frontend/src/component/feature/view-component.jsx +++ b/frontend/src/component/feature/view-component.jsx @@ -53,7 +53,9 @@ export default class ViewFeatureToggleComponent extends React.Component { return ; } else if (TABS[activeTab] === TABS.strategies) { if (this.isFeatureView) { - return ; + return ( + + ); } return ; } else { diff --git a/frontend/src/page/features/create.js b/frontend/src/page/features/create.js index 7e65314b7d..8531c74052 100644 --- a/frontend/src/page/features/create.js +++ b/frontend/src/page/features/create.js @@ -1,6 +1,11 @@ import React from 'react'; import AddFeatureToggleForm from '../../component/feature/form/form-add-feature-container'; +import PropTypes from 'prop-types'; -const render = () => ; +const render = ({ history }) => ; + +render.propTypes = { + history: PropTypes.object.isRequired, +}; export default render; diff --git a/frontend/src/page/features/index.js b/frontend/src/page/features/index.js index ccd602443e..bf18de3066 100644 --- a/frontend/src/page/features/index.js +++ b/frontend/src/page/features/index.js @@ -1,6 +1,11 @@ import React from 'react'; import FeatureListContainer from './../../component/feature/list-container'; +import PropTypes from 'prop-types'; -const render = () => ; +const render = ({ history }) => ; + +render.propTypes = { + history: PropTypes.object.isRequired, +}; export default render; From 28600218226a9efc2d8099b16949c8d585e08aa6 Mon Sep 17 00:00:00 2001 From: ivaosthu Date: Mon, 13 Aug 2018 09:21:23 +0200 Subject: [PATCH 4/7] fix(router): Add 'history' prop to the archive-list. --- frontend/src/page/archive/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/page/archive/index.js b/frontend/src/page/archive/index.js index 9c5cfba1df..bac016f71a 100644 --- a/frontend/src/page/archive/index.js +++ b/frontend/src/page/archive/index.js @@ -2,9 +2,10 @@ import React from 'react'; import Archive from '../../component/archive/archive-list-container'; import PropTypes from 'prop-types'; -const render = ({ match }) => ; +const render = ({ match: { params }, history }) => ; render.propTypes = { match: PropTypes.object, + history: PropTypes.object, }; export default render; From 8e30022282f7336873da385510656ddb946fe577 Mon Sep 17 00:00:00 2001 From: ivaosthu Date: Mon, 13 Aug 2018 09:26:05 +0200 Subject: [PATCH 5/7] fix(StrategiesList): Added unique render key. --- frontend/src/component/feature/form/strategies-list.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/component/feature/form/strategies-list.jsx b/frontend/src/component/feature/form/strategies-list.jsx index b6f8936c2c..dabaf01aa6 100644 --- a/frontend/src/component/feature/form/strategies-list.jsx +++ b/frontend/src/component/feature/form/strategies-list.jsx @@ -24,7 +24,7 @@ class StrategiesList extends React.Component { const blocks = configuredStrategies.map((strategy, i) => ( Date: Mon, 13 Aug 2018 09:54:18 +0200 Subject: [PATCH 6/7] fix(react-router): Make sure logout still works. --- .../menu/__tests__/__snapshots__/routes-test.jsx.snap | 6 ++++-- frontend/src/component/menu/routes.js | 3 ++- .../component/user/authentication-simple-component.jsx | 8 +------- frontend/src/page/user/logout.js | 7 ++++++- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/frontend/src/component/menu/__tests__/__snapshots__/routes-test.jsx.snap b/frontend/src/component/menu/__tests__/__snapshots__/routes-test.jsx.snap index cf94282c9c..0820089161 100644 --- a/frontend/src/component/menu/__tests__/__snapshots__/routes-test.jsx.snap +++ b/frontend/src/component/menu/__tests__/__snapshots__/routes-test.jsx.snap @@ -33,8 +33,9 @@ Array [ "title": "Applications", }, Object { + "component": [Function], "icon": "exit_to_app", - "path": "logout", + "path": "/logout", "title": "Sign out", }, ] @@ -115,8 +116,9 @@ Array [ "title": "Applications", }, Object { + "component": [Function], "icon": "exit_to_app", - "path": "logout", + "path": "/logout", "title": "Sign out", }, ] diff --git a/frontend/src/component/menu/routes.js b/frontend/src/component/menu/routes.js index 1a5acac0d5..4854810352 100644 --- a/frontend/src/component/menu/routes.js +++ b/frontend/src/component/menu/routes.js @@ -10,6 +10,7 @@ import ShowArchive from '../../page/archive/show'; import Archive from '../../page/archive'; import Applications from '../../page/applications'; import ApplicationView from '../../page/applications/view'; +import LogoutFeatures from '../../page/user/logout'; export const routes = [ // Features @@ -39,7 +40,7 @@ export const routes = [ { path: '/applications/:name', title: ':name', parent: '/applications', component: ApplicationView }, { path: '/applications', title: 'Applications', icon: 'apps', component: Applications }, - { path: 'logout', title: 'Sign out', icon: 'exit_to_app' }, + { path: '/logout', title: 'Sign out', icon: 'exit_to_app', component: LogoutFeatures }, ]; export const getRoute = path => routes.find(route => route.path === path); diff --git a/frontend/src/component/user/authentication-simple-component.jsx b/frontend/src/component/user/authentication-simple-component.jsx index ad1dbd4d04..d00a5f3959 100644 --- a/frontend/src/component/user/authentication-simple-component.jsx +++ b/frontend/src/component/user/authentication-simple-component.jsx @@ -7,7 +7,6 @@ class SimpleAuthenticationComponent extends React.Component { authDetails: PropTypes.object.isRequired, unsecureLogin: PropTypes.func.isRequired, fetchFeatureToggles: PropTypes.func.isRequired, - history: PropTypes.object.isRequired, }; handleSubmit = evt => { @@ -16,12 +15,7 @@ class SimpleAuthenticationComponent extends React.Component { const user = { email }; const path = evt.target.action; - this.props - .unsecureLogin(path, user) - .then(this.props.fetchFeatureToggles) - .then(() => { - this.props.history.push('/features'); - }); + this.props.unsecureLogin(path, user).then(this.props.fetchFeatureToggles); }; render() { diff --git a/frontend/src/page/user/logout.js b/frontend/src/page/user/logout.js index d2b3217548..710ea681cd 100644 --- a/frontend/src/page/user/logout.js +++ b/frontend/src/page/user/logout.js @@ -1,6 +1,11 @@ import React from 'react'; import FeatureListContainer from './../../component/feature/list-container'; +import PropTypes from 'prop-types'; -const render = () => ; +const render = ({ history }) => ; + +render.propTypes = { + history: PropTypes.object.isRequired, +}; export default render; From 2f2581298b9bbc119e6f78112d741ca5a773a62a Mon Sep 17 00:00:00 2001 From: ivaosthu Date: Mon, 13 Aug 2018 13:13:27 +0200 Subject: [PATCH 7/7] fix(eslint): ovveride test rules in root .eslintrc file --- frontend/.eslintrc | 8 +++++++- frontend/src/__tests__/.eslintrc | 5 ----- frontend/src/component/api/__tests__/.eslintrc | 5 ----- frontend/src/component/application/__tests__/.eslintrc | 5 ----- .../src/component/client-instance/__tests__/.eslintrc | 5 ----- frontend/src/component/common/__tests__/.eslintrc | 5 ----- frontend/src/component/feature/__tests__/.eslintrc | 5 ----- frontend/src/component/menu/__tests__/.eslintrc | 5 ----- frontend/src/store/api/__tests__/.eslintrc | 5 ----- 9 files changed, 7 insertions(+), 41 deletions(-) delete mode 100644 frontend/src/__tests__/.eslintrc delete mode 100644 frontend/src/component/api/__tests__/.eslintrc delete mode 100644 frontend/src/component/application/__tests__/.eslintrc delete mode 100644 frontend/src/component/client-instance/__tests__/.eslintrc delete mode 100644 frontend/src/component/common/__tests__/.eslintrc delete mode 100644 frontend/src/component/feature/__tests__/.eslintrc delete mode 100644 frontend/src/component/menu/__tests__/.eslintrc delete mode 100644 frontend/src/store/api/__tests__/.eslintrc diff --git a/frontend/.eslintrc b/frontend/.eslintrc index 3f3e372e9e..196241c50d 100644 --- a/frontend/.eslintrc +++ b/frontend/.eslintrc @@ -15,5 +15,11 @@ "printWidth": 120 } ] - } + }, + "overrides": [ + { + "files": ["**/__tests__/*"], + "env": { "jest": true } + } + ] } diff --git a/frontend/src/__tests__/.eslintrc b/frontend/src/__tests__/.eslintrc deleted file mode 100644 index eba2077219..0000000000 --- a/frontend/src/__tests__/.eslintrc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "env": { - "jest": true - } -} diff --git a/frontend/src/component/api/__tests__/.eslintrc b/frontend/src/component/api/__tests__/.eslintrc deleted file mode 100644 index eba2077219..0000000000 --- a/frontend/src/component/api/__tests__/.eslintrc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "env": { - "jest": true - } -} diff --git a/frontend/src/component/application/__tests__/.eslintrc b/frontend/src/component/application/__tests__/.eslintrc deleted file mode 100644 index eba2077219..0000000000 --- a/frontend/src/component/application/__tests__/.eslintrc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "env": { - "jest": true - } -} diff --git a/frontend/src/component/client-instance/__tests__/.eslintrc b/frontend/src/component/client-instance/__tests__/.eslintrc deleted file mode 100644 index eba2077219..0000000000 --- a/frontend/src/component/client-instance/__tests__/.eslintrc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "env": { - "jest": true - } -} diff --git a/frontend/src/component/common/__tests__/.eslintrc b/frontend/src/component/common/__tests__/.eslintrc deleted file mode 100644 index eba2077219..0000000000 --- a/frontend/src/component/common/__tests__/.eslintrc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "env": { - "jest": true - } -} diff --git a/frontend/src/component/feature/__tests__/.eslintrc b/frontend/src/component/feature/__tests__/.eslintrc deleted file mode 100644 index eba2077219..0000000000 --- a/frontend/src/component/feature/__tests__/.eslintrc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "env": { - "jest": true - } -} diff --git a/frontend/src/component/menu/__tests__/.eslintrc b/frontend/src/component/menu/__tests__/.eslintrc deleted file mode 100644 index eba2077219..0000000000 --- a/frontend/src/component/menu/__tests__/.eslintrc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "env": { - "jest": true - } -} diff --git a/frontend/src/store/api/__tests__/.eslintrc b/frontend/src/store/api/__tests__/.eslintrc deleted file mode 100644 index eba2077219..0000000000 --- a/frontend/src/store/api/__tests__/.eslintrc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "env": { - "jest": true - } -}