From 9341c4660ce63a9bd0367693e43d18bda236bca0 Mon Sep 17 00:00:00 2001 From: Marliana Lara Date: Mon, 3 Dec 2018 13:17:53 -0500 Subject: [PATCH 1/4] Update active nav item based on url --- __tests__/App.test.jsx | 35 ++- src/App.jsx | 545 +++++++++++++++++++++-------------------- src/index.jsx | 7 +- 3 files changed, 306 insertions(+), 281 deletions(-) diff --git a/__tests__/App.test.jsx b/__tests__/App.test.jsx index e07a1984d5..e9d8c75fbf 100644 --- a/__tests__/App.test.jsx +++ b/__tests__/App.test.jsx @@ -1,5 +1,8 @@ import React from 'react'; +import { HashRouter as Router } from 'react-router-dom'; import { shallow, mount } from 'enzyme'; +import { createMemoryHistory } from 'history' + import App from '../src/App'; import api from '../src/api'; import { API_LOGOUT } from '../src/endpoints'; @@ -21,7 +24,7 @@ describe('', () => { api.isAuthenticated = jest.fn(); api.isAuthenticated.mockReturnValue(false); - const appWrapper = mount(); + const appWrapper = mount(); const login = appWrapper.find(Login); expect(login.length).toBe(1); @@ -33,7 +36,7 @@ describe('', () => { api.isAuthenticated = jest.fn(); api.isAuthenticated.mockReturnValue(true); - const appWrapper = mount(); + const appWrapper = mount(); const dashboard = appWrapper.find(Dashboard); expect(dashboard.length).toBe(1); @@ -42,39 +45,47 @@ describe('', () => { }); test('onNavSelect sets state.activeItem and state.activeGroup', () => { - const appWrapper = shallow(); - appWrapper.instance().onNavSelect({ itemId: 'foo', groupId: 'bar' }); - expect(appWrapper.state().activeItem).toBe('foo'); + const history = createMemoryHistory('/jobs'); + const appWrapper = shallow(); + + appWrapper.instance().onNavSelect({ groupId: 'bar' }); expect(appWrapper.state().activeGroup).toBe('bar'); }); test('onNavToggle sets state.isNavOpen to opposite', () => { - const appWrapper = shallow(); + const history = createMemoryHistory('/jobs'); + + const appWrapper = shallow(); expect(appWrapper.state().isNavOpen).toBe(true); appWrapper.instance().onNavToggle(); expect(appWrapper.state().isNavOpen).toBe(false); }); test('onLogoClick sets selected nav back to defaults', () => { - const appWrapper = shallow(); + const history = createMemoryHistory('/jobs'); + + const appWrapper = shallow(); appWrapper.setState({ activeGroup: 'foo', activeItem: 'bar' }); expect(appWrapper.state().activeItem).toBe('bar'); expect(appWrapper.state().activeGroup).toBe('foo'); appWrapper.instance().onLogoClick(); - expect(appWrapper.state().activeItem).toBe(DEFAULT_ACTIVE_ITEM); expect(appWrapper.state().activeGroup).toBe(DEFAULT_ACTIVE_GROUP); }); test('api.logout called from logout button', async () => { api.get = jest.fn().mockImplementation(() => Promise.resolve({})); - const appWrapper = mount(); - const logoutButton = appWrapper.find('LogoutButton'); + let appWrapper = mount(); + let logoutButton = appWrapper.find('LogoutButton'); logoutButton.props().onDevLogout(); appWrapper.setState({ activeGroup: 'foo', activeItem: 'bar' }); + + await asyncFlush(); + expect(api.get).toHaveBeenCalledTimes(1); expect(api.get).toHaveBeenCalledWith(API_LOGOUT); - await asyncFlush(); - expect(appWrapper.state().activeItem).toBe(DEFAULT_ACTIVE_ITEM); + + console.log(appWrapper.state()); expect(appWrapper.state().activeGroup).toBe(DEFAULT_ACTIVE_GROUP); }); + }); diff --git a/src/App.jsx b/src/App.jsx index 62edb76d3a..ac88f3f7f6 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,8 +1,8 @@ import React, { Fragment } from 'react'; import { - HashRouter as Router, Redirect, Switch, + withRouter } from 'react-router-dom'; import { @@ -61,13 +61,11 @@ class App extends React.Component { this.state = { isNavOpen, activeGroup: 'views_group', - activeItem: 'views_group_dashboard' }; } onNavSelect = result => { this.setState({ - activeItem: result.itemId, activeGroup: result.groupId }); }; @@ -77,16 +75,31 @@ class App extends React.Component { }; onLogoClick = () => { - this.setState({ activeGroup: 'views_group', activeItem: 'views_group_dashboard' }); + this.setState({ activeGroup: 'views_group' }); } onDevLogout = async () => { + console.log('called') await api.get(API_LOGOUT); this.setState({ activeGroup: 'views_group', activeItem: 'views_group_dashboard' }); + + console.log(this.state); } + expand = (path, group) => { + const { history } = this.props; + const { activeGroup } = this.state; + + const currentPath = history.location.pathname.split('/')[1]; + if ((path === currentPath) && (group !== activeGroup)) { + this.setState({ activeGroup: group }); + } + return (path === currentPath); + }; + render () { - const { activeItem, activeGroup, isNavOpen } = this.state; + console.log('render'); + const { activeGroup, isNavOpen } = this.state; const { logo, loginInfo } = this.props; const PageToolbar = ( @@ -103,279 +116,277 @@ class App extends React.Component { ); return ( - - - - - api.isAuthenticated()} redirectPath="/" path="/login" component={() => } /> - - } - toolbar={PageToolbar} - showNavToggle - onNavToggle={this.onNavToggle} - /> - )} - sidebar={( - - - + + + api.isAuthenticated()} redirectPath="/" path="/login" component={() => } /> + + } + toolbar={PageToolbar} + showNavToggle + onNavToggle={this.onNavToggle} + /> + )} + sidebar={( + + + + - - Dashboard - - - Jobs - - - Schedules - - - My View - - - + + Jobs + + + Schedules + + + My View + + + + - - Templates - - - Credentials - - - Projects - - - Inventories - - - Inventory Scripts - - - + + Credentials + + + Projects + + + Inventories + + + Inventory Scripts + + + + - - Organizations - - - Users - - - Teams - - - + + Users + + + Teams + + + + - - Credential Types - - - Notification Templates - - - Management Jobs - - - Instance Groups - - - Applications - - - + + Notification Templates + + + Management Jobs + + + Instance Groups + + + Applications + + + + - - Authentication - - - Jobs - - - System - - - User Interface - - - - - )} - /> - )} - useCondensed - > - !api.isAuthenticated()} redirectPath="/login" exact path="/" component={() => ()} /> - !api.isAuthenticated()} redirectPath="/login" path="/home" component={Dashboard} /> - !api.isAuthenticated()} redirectPath="/login" path="/jobs" component={Jobs} /> - !api.isAuthenticated()} redirectPath="/login" path="/schedules" component={Schedules} /> - !api.isAuthenticated()} redirectPath="/login" path="/portal" component={Portal} /> - !api.isAuthenticated()} redirectPath="/login" path="/templates" component={Templates} /> - !api.isAuthenticated()} redirectPath="/login" path="/credentials" component={Credentials} /> - !api.isAuthenticated()} redirectPath="/login" path="/projects" component={Projects} /> - !api.isAuthenticated()} redirectPath="/login" path="/inventories" component={Inventories} /> - !api.isAuthenticated()} redirectPath="/login" path="/inventory_scripts" component={InventoryScripts} /> - !api.isAuthenticated()} redirectPath="/login" path="/organizations" component={Organizations} /> - !api.isAuthenticated()} redirectPath="/login" path="/users" component={Users} /> - !api.isAuthenticated()} redirectPath="/login" path="/teams" component={Teams} /> - !api.isAuthenticated()} redirectPath="/login" path="/credential_types" component={CredentialTypes} /> - !api.isAuthenticated()} redirectPath="/login" path="/notification_templates" component={NotificationTemplates} /> - !api.isAuthenticated()} redirectPath="/login" path="/management_jobs" component={ManagementJobs} /> - !api.isAuthenticated()} redirectPath="/login" path="/instance_groups" component={InstanceGroups} /> - !api.isAuthenticated()} redirectPath="/login" path="/applications" component={Applications} /> - !api.isAuthenticated()} redirectPath="/login" path="/auth_settings" component={AuthSettings} /> - !api.isAuthenticated()} redirectPath="/login" path="/jobs_settings" component={JobsSettings} /> - !api.isAuthenticated()} redirectPath="/login" path="/system_settings" component={SystemSettings} /> - !api.isAuthenticated()} redirectPath="/login" path="/ui_settings" component={UISettings} /> - !api.isAuthenticated()} redirectPath="/login" path="/license" component={License} /> - - - - - + Authentication + + + Jobs + + + System + + + User Interface + + + + + )} + /> + )} + useCondensed + > + !api.isAuthenticated()} redirectPath="/login" exact path="/" component={() => ()} /> + !api.isAuthenticated()} redirectPath="/login" path="/home" component={Dashboard} /> + !api.isAuthenticated()} redirectPath="/login" path="/jobs" component={Jobs} /> + !api.isAuthenticated()} redirectPath="/login" path="/schedules" component={Schedules} /> + !api.isAuthenticated()} redirectPath="/login" path="/portal" component={Portal} /> + !api.isAuthenticated()} redirectPath="/login" path="/templates" component={Templates} /> + !api.isAuthenticated()} redirectPath="/login" path="/credentials" component={Credentials} /> + !api.isAuthenticated()} redirectPath="/login" path="/projects" component={Projects} /> + !api.isAuthenticated()} redirectPath="/login" path="/inventories" component={Inventories} /> + !api.isAuthenticated()} redirectPath="/login" path="/inventory_scripts" component={InventoryScripts} /> + !api.isAuthenticated()} redirectPath="/login" path="/organizations" component={Organizations} /> + !api.isAuthenticated()} redirectPath="/login" path="/users" component={Users} /> + !api.isAuthenticated()} redirectPath="/login" path="/teams" component={Teams} /> + !api.isAuthenticated()} redirectPath="/login" path="/credential_types" component={CredentialTypes} /> + !api.isAuthenticated()} redirectPath="/login" path="/notification_templates" component={NotificationTemplates} /> + !api.isAuthenticated()} redirectPath="/login" path="/management_jobs" component={ManagementJobs} /> + !api.isAuthenticated()} redirectPath="/login" path="/instance_groups" component={InstanceGroups} /> + !api.isAuthenticated()} redirectPath="/login" path="/applications" component={Applications} /> + !api.isAuthenticated()} redirectPath="/login" path="/auth_settings" component={AuthSettings} /> + !api.isAuthenticated()} redirectPath="/login" path="/jobs_settings" component={JobsSettings} /> + !api.isAuthenticated()} redirectPath="/login" path="/system_settings" component={SystemSettings} /> + !api.isAuthenticated()} redirectPath="/login" path="/ui_settings" component={UISettings} /> + !api.isAuthenticated()} redirectPath="/login" path="/license" component={License} /> + + + + ); } } -export default App; +export default withRouter(App); diff --git a/src/index.jsx b/src/index.jsx index cd653d3c53..24a5eb8576 100644 --- a/src/index.jsx +++ b/src/index.jsx @@ -1,6 +1,9 @@ import React from 'react'; import { render } from 'react-dom'; +import { + HashRouter as Router +} from 'react-router-dom'; import App from './App'; import api from './api'; import { API_ROOT } from './endpoints'; @@ -15,8 +18,8 @@ import './components/DataListToolbar/styles.scss'; const el = document.getElementById('app'); const main = async () => { - const { custom_logo, custom_login_info } = await api.get(API_ROOT); - render(, el); + const { custom_logo, custom_login_info } = await api.get(API_ROOT); + render(, el); }; main(); From f83b59cb4806d4a7441e1dfd07c7b33a7ab9fd32 Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Thu, 15 Nov 2018 16:45:50 -0500 Subject: [PATCH 2/4] working commit of group and nav selection based on url --- src/App.jsx | 308 ++++++++++++++++------------------------------------ 1 file changed, 93 insertions(+), 215 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index ac88f3f7f6..faaba35be8 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -53,23 +53,52 @@ import Teams from './pages/Teams'; import Templates from './pages/Templates'; import Users from './pages/Users'; +const SideNavItems = ({ items, history }) => { + const currentPath = history.location.pathname.replace(/^\//, ''); + let activeGroup; + if (currentPath !== '') { + const groupPaths = items.map(({ groupName, routes }) => ({ + groupName, + paths: routes.map(({ path }) => path) + })); + [{ groupName: activeGroup }] = groupPaths + .filter(({ paths }) => paths.indexOf(currentPath) > -1); + } else { + activeGroup = 'views'; + } + + return (items.map(({ title, groupName, routes }) => ( + + {routes.map(({ path, title: itemTitle }) => ( + + {itemTitle} + + ))} + + ))); +}; + class App extends React.Component { constructor (props) { super(props); const isNavOpen = typeof window !== 'undefined' && window.innerWidth >= parseInt(breakpointMd.value, 10); this.state = { - isNavOpen, - activeGroup: 'views_group', + isNavOpen }; } - onNavSelect = result => { - this.setState({ - activeGroup: result.groupId - }); - }; - onNavToggle = () => { this.setState(({ isNavOpen }) => ({ isNavOpen: !isNavOpen })); }; @@ -98,9 +127,8 @@ class App extends React.Component { }; render () { - console.log('render'); - const { activeGroup, isNavOpen } = this.state; - const { logo, loginInfo } = this.props; + const { isNavOpen } = this.state; + const { logo, loginInfo, history } = this.props; const PageToolbar = ( @@ -146,211 +174,61 @@ class App extends React.Component { + )} From 00c9ae137676dc59c4191823260a5b92f31ce311 Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Tue, 4 Dec 2018 11:33:04 -0500 Subject: [PATCH 3/4] update map function to be chained --- src/App.jsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index faaba35be8..8724016cce 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -57,11 +57,11 @@ const SideNavItems = ({ items, history }) => { const currentPath = history.location.pathname.replace(/^\//, ''); let activeGroup; if (currentPath !== '') { - const groupPaths = items.map(({ groupName, routes }) => ({ - groupName, - paths: routes.map(({ path }) => path) - })); - [{ groupName: activeGroup }] = groupPaths + [{ groupName: activeGroup }] = items + .map(({ groupName, routes }) => ({ + groupName, + paths: routes.map(({ path }) => path) + })) .filter(({ paths }) => paths.indexOf(currentPath) > -1); } else { activeGroup = 'views'; From a31ef24be6c0e9abc1618830073a8e1e7dccf9f4 Mon Sep 17 00:00:00 2001 From: Marliana Lara Date: Wed, 5 Dec 2018 07:56:53 -0500 Subject: [PATCH 4/4] Remove calling setState from render --- __tests__/App.test.jsx | 31 +++------------ src/App.jsx | 88 ++++++++++++++++++++++++++++++++++-------- 2 files changed, 78 insertions(+), 41 deletions(-) diff --git a/__tests__/App.test.jsx b/__tests__/App.test.jsx index e9d8c75fbf..7b98adafff 100644 --- a/__tests__/App.test.jsx +++ b/__tests__/App.test.jsx @@ -1,8 +1,6 @@ import React from 'react'; import { HashRouter as Router } from 'react-router-dom'; import { shallow, mount } from 'enzyme'; -import { createMemoryHistory } from 'history' - import App from '../src/App'; import api from '../src/api'; import { API_LOGOUT } from '../src/endpoints'; @@ -44,27 +42,15 @@ describe('', () => { expect(login.length).toBe(0); }); - test('onNavSelect sets state.activeItem and state.activeGroup', () => { - const history = createMemoryHistory('/jobs'); - const appWrapper = shallow(); - - appWrapper.instance().onNavSelect({ groupId: 'bar' }); - expect(appWrapper.state().activeGroup).toBe('bar'); - }); - test('onNavToggle sets state.isNavOpen to opposite', () => { - const history = createMemoryHistory('/jobs'); - - const appWrapper = shallow(); + const appWrapper = shallow(); expect(appWrapper.state().isNavOpen).toBe(true); appWrapper.instance().onNavToggle(); expect(appWrapper.state().isNavOpen).toBe(false); }); test('onLogoClick sets selected nav back to defaults', () => { - const history = createMemoryHistory('/jobs'); - - const appWrapper = shallow(); + const appWrapper = shallow(); appWrapper.setState({ activeGroup: 'foo', activeItem: 'bar' }); expect(appWrapper.state().activeItem).toBe('bar'); expect(appWrapper.state().activeGroup).toBe('foo'); @@ -74,18 +60,13 @@ describe('', () => { test('api.logout called from logout button', async () => { api.get = jest.fn().mockImplementation(() => Promise.resolve({})); - let appWrapper = mount(); - let logoutButton = appWrapper.find('LogoutButton'); - logoutButton.props().onDevLogout(); + const appWrapper = shallow(); + appWrapper.instance().onDevLogout(); appWrapper.setState({ activeGroup: 'foo', activeItem: 'bar' }); - - await asyncFlush(); - expect(api.get).toHaveBeenCalledTimes(1); expect(api.get).toHaveBeenCalledWith(API_LOGOUT); - - console.log(appWrapper.state()); + await asyncFlush(); + expect(appWrapper.state().activeItem).toBe(DEFAULT_ACTIVE_ITEM); expect(appWrapper.state().activeGroup).toBe(DEFAULT_ACTIVE_GROUP); }); - }); diff --git a/src/App.jsx b/src/App.jsx index 8724016cce..f400ce3e53 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -54,7 +54,7 @@ import Templates from './pages/Templates'; import Users from './pages/Users'; const SideNavItems = ({ items, history }) => { - const currentPath = history.location.pathname.replace(/^\//, ''); + const currentPath = history.location.pathname.split('/')[1]; let activeGroup; if (currentPath !== '') { [{ groupName: activeGroup }] = items @@ -108,24 +108,10 @@ class App extends React.Component { } onDevLogout = async () => { - console.log('called') await api.get(API_LOGOUT); this.setState({ activeGroup: 'views_group', activeItem: 'views_group_dashboard' }); - - console.log(this.state); } - expand = (path, group) => { - const { history } = this.props; - const { activeGroup } = this.state; - - const currentPath = history.location.pathname.split('/')[1]; - if ((path === currentPath) && (group !== activeGroup)) { - this.setState({ activeGroup: group }); - } - return (path === currentPath); - }; - render () { const { isNavOpen } = this.state; const { logo, loginInfo, history } = this.props; @@ -197,7 +183,7 @@ class App extends React.Component { }, { path: 'portal', - title: 'Portal' + title: 'Portal Mode' }, ] }, @@ -226,6 +212,76 @@ class App extends React.Component { title: 'Inventory Scripts' } ] + }, + { + groupName: 'access', + title: 'Access', + routes: [ + { + path: 'organizations', + title: 'Organizations' + }, + { + path: 'users', + title: 'Users' + }, + { + path: 'teams', + title: 'Teams' + } + ] + }, + { + groupName: 'administration', + title: 'Administration', + routes: [ + { + path: 'credential_types', + title: 'Credential Types', + }, + { + path: 'notification_templates', + title: 'Notifications' + }, + { + path: 'management_jobs', + title: 'Management Jobs' + }, + { + path: 'instance_groups', + title: 'Instance Groups' + }, + { + path: 'applications', + title: 'Integrations' + } + ] + }, + { + groupName: 'settings', + title: 'Settings', + routes: [ + { + path: 'auth_settings', + title: 'Authentication', + }, + { + path: 'jobs_settings', + title: 'Jobs' + }, + { + path: 'system_settings', + title: 'System' + }, + { + path: 'ui_settings', + title: 'User Interface' + }, + { + path: 'license', + title: 'License' + } + ] } ]} />