mirror of https://github.com/grafana/grafana
Reactify sidebar (#13091)
* created react component and moved markdown * extracting components * Broke out parts into components * tests * Flattened file structure * Tests * made instances typed in test * typing * function instead of variable * updated user model with missing properties * added full set of properties to user mock * redone from variable to function * refactor: minor refactorings of #13091 * removed loggingpull/12934/merge
parent
0f326f18dc
commit
cab6861d27
@ -0,0 +1,96 @@ |
||||
import React from 'react'; |
||||
import { shallow } from 'enzyme'; |
||||
import BottomNavLinks from './BottomNavLinks'; |
||||
import appEvents from '../../app_events'; |
||||
|
||||
jest.mock('../../app_events', () => ({ |
||||
emit: jest.fn(), |
||||
})); |
||||
|
||||
const setup = (propOverrides?: object) => { |
||||
const props = Object.assign( |
||||
{ |
||||
link: {}, |
||||
user: { |
||||
isGrafanaAdmin: false, |
||||
isSignedIn: false, |
||||
orgCount: 2, |
||||
orgRole: '', |
||||
orgId: 1, |
||||
orgName: 'Grafana', |
||||
timezone: 'UTC', |
||||
helpFlags1: 1, |
||||
lightTheme: false, |
||||
hasEditPermissionInFolders: false, |
||||
}, |
||||
}, |
||||
propOverrides |
||||
); |
||||
return shallow(<BottomNavLinks {...props} />); |
||||
}; |
||||
|
||||
describe('Render', () => { |
||||
it('should render component', () => { |
||||
const wrapper = setup(); |
||||
|
||||
expect(wrapper).toMatchSnapshot(); |
||||
}); |
||||
|
||||
it('should render organisation switcher', () => { |
||||
const wrapper = setup({ |
||||
link: { |
||||
showOrgSwitcher: true, |
||||
}, |
||||
}); |
||||
|
||||
expect(wrapper).toMatchSnapshot(); |
||||
}); |
||||
|
||||
it('should render subtitle', () => { |
||||
const wrapper = setup({ |
||||
link: { |
||||
subTitle: 'subtitle', |
||||
}, |
||||
}); |
||||
|
||||
expect(wrapper).toMatchSnapshot(); |
||||
}); |
||||
|
||||
it('should render children', () => { |
||||
const wrapper = setup({ |
||||
link: { |
||||
children: [ |
||||
{ |
||||
id: '1', |
||||
}, |
||||
{ |
||||
id: '2', |
||||
}, |
||||
{ |
||||
id: '3', |
||||
}, |
||||
{ |
||||
id: '4', |
||||
hideFromMenu: true, |
||||
}, |
||||
], |
||||
}, |
||||
}); |
||||
|
||||
expect(wrapper).toMatchSnapshot(); |
||||
}); |
||||
}); |
||||
|
||||
describe('Functions', () => { |
||||
describe('item clicked', () => { |
||||
const wrapper = setup(); |
||||
const mockEvent = { preventDefault: jest.fn() }; |
||||
it('should emit show modal event if url matches shortcut', () => { |
||||
const child = { url: '/shortcuts' }; |
||||
const instance = wrapper.instance() as BottomNavLinks; |
||||
instance.itemClicked(mockEvent, child); |
||||
|
||||
expect(appEvents.emit).toHaveBeenCalledWith('show-modal', { templateHtml: '<help-modal></help-modal>' }); |
||||
}); |
||||
}); |
||||
}); |
||||
@ -0,0 +1,78 @@ |
||||
import React, { PureComponent } from 'react'; |
||||
import appEvents from '../../app_events'; |
||||
import { User } from '../../services/context_srv'; |
||||
|
||||
export interface Props { |
||||
link: any; |
||||
user: User; |
||||
} |
||||
|
||||
class BottomNavLinks extends PureComponent<Props> { |
||||
itemClicked = (event, child) => { |
||||
if (child.url === '/shortcuts') { |
||||
event.preventDefault(); |
||||
appEvents.emit('show-modal', { |
||||
templateHtml: '<help-modal></help-modal>', |
||||
}); |
||||
} |
||||
}; |
||||
|
||||
switchOrg = () => { |
||||
appEvents.emit('show-modal', { |
||||
templateHtml: '<org-switcher dismiss="dismiss()"></org-switcher>', |
||||
}); |
||||
}; |
||||
|
||||
render() { |
||||
const { link, user } = this.props; |
||||
return ( |
||||
<div className="sidemenu-item dropdown dropup"> |
||||
<a href={link.url} className="sidemenu-link" target={link.target}> |
||||
<span className="icon-circle sidemenu-icon"> |
||||
{link.icon && <i className={link.icon} />} |
||||
{link.img && <img src={link.img} />} |
||||
</span> |
||||
</a> |
||||
<ul className="dropdown-menu dropdown-menu--sidemenu" role="menu"> |
||||
{link.subTitle && ( |
||||
<li className="sidemenu-subtitle"> |
||||
<span className="sidemenu-item-text">{link.subTitle}</span> |
||||
</li> |
||||
)} |
||||
{link.showOrgSwitcher && ( |
||||
<li className="sidemenu-org-switcher"> |
||||
<a onClick={this.switchOrg}> |
||||
<div> |
||||
<div className="sidemenu-org-switcher__org-name">{user.orgName}</div> |
||||
<div className="sidemenu-org-switcher__org-current">Current Org:</div> |
||||
</div> |
||||
<div className="sidemenu-org-switcher__switch"> |
||||
<i className="fa fa-fw fa-random" />Switch |
||||
</div> |
||||
</a> |
||||
</li> |
||||
)} |
||||
{link.children && |
||||
link.children.map((child, index) => { |
||||
if (!child.hideFromMenu) { |
||||
return ( |
||||
<li className={child.divider} key={`${child.text}-${index}`}> |
||||
<a href={child.url} target={child.target} onClick={event => this.itemClicked(event, child)}> |
||||
{child.icon && <i className={child.icon} />} |
||||
{child.text} |
||||
</a> |
||||
</li> |
||||
); |
||||
} |
||||
return null; |
||||
})} |
||||
<li className="side-menu-header"> |
||||
<span className="sidemenu-item-text">{link.text}</span> |
||||
</li> |
||||
</ul> |
||||
</div> |
||||
); |
||||
} |
||||
} |
||||
|
||||
export default BottomNavLinks; |
||||
@ -0,0 +1,44 @@ |
||||
import React from 'react'; |
||||
import { shallow } from 'enzyme'; |
||||
import BottomSection from './BottomSection'; |
||||
|
||||
jest.mock('../../config', () => ({ |
||||
bootData: { |
||||
navTree: [ |
||||
{ |
||||
id: 'profile', |
||||
hideFromMenu: true, |
||||
}, |
||||
{ |
||||
hideFromMenu: true, |
||||
}, |
||||
{ |
||||
hideFromMenu: false, |
||||
}, |
||||
{ |
||||
hideFromMenu: true, |
||||
}, |
||||
], |
||||
}, |
||||
user: { |
||||
orgCount: 5, |
||||
orgName: 'Grafana', |
||||
}, |
||||
})); |
||||
|
||||
jest.mock('app/core/services/context_srv', () => ({ |
||||
contextSrv: { |
||||
sidemenu: true, |
||||
isSignedIn: false, |
||||
isGrafanaAdmin: false, |
||||
hasEditPermissionFolders: false, |
||||
}, |
||||
})); |
||||
|
||||
describe('Render', () => { |
||||
it('should render component', () => { |
||||
const wrapper = shallow(<BottomSection />); |
||||
|
||||
expect(wrapper).toMatchSnapshot(); |
||||
}); |
||||
}); |
||||
@ -0,0 +1,29 @@ |
||||
import React from 'react'; |
||||
import _ from 'lodash'; |
||||
import SignIn from './SignIn'; |
||||
import BottomNavLinks from './BottomNavLinks'; |
||||
import { contextSrv } from 'app/core/services/context_srv'; |
||||
import config from '../../config'; |
||||
|
||||
export default function BottomSection() { |
||||
const navTree = _.cloneDeep(config.bootData.navTree); |
||||
const bottomNav = _.filter(navTree, item => item.hideFromMenu); |
||||
const isSignedIn = contextSrv.isSignedIn; |
||||
const user = contextSrv.user; |
||||
|
||||
if (user && user.orgCount > 1) { |
||||
const profileNode = _.find(bottomNav, { id: 'profile' }); |
||||
if (profileNode) { |
||||
profileNode.showOrgSwitcher = true; |
||||
} |
||||
} |
||||
|
||||
return ( |
||||
<div className="sidemenu__bottom"> |
||||
{!isSignedIn && <SignIn />} |
||||
{bottomNav.map((link, index) => { |
||||
return <BottomNavLinks link={link} user={user} key={`${link.url}-${index}`} />; |
||||
})} |
||||
</div> |
||||
); |
||||
} |
||||
@ -0,0 +1,35 @@ |
||||
import React from 'react'; |
||||
import { shallow } from 'enzyme'; |
||||
import DropDownChild from './DropDownChild'; |
||||
|
||||
const setup = (propOverrides?: object) => { |
||||
const props = Object.assign( |
||||
{ |
||||
child: { |
||||
divider: true, |
||||
}, |
||||
}, |
||||
propOverrides |
||||
); |
||||
|
||||
return shallow(<DropDownChild {...props} />); |
||||
}; |
||||
|
||||
describe('Render', () => { |
||||
it('should render component', () => { |
||||
const wrapper = setup(); |
||||
|
||||
expect(wrapper).toMatchSnapshot(); |
||||
}); |
||||
|
||||
it('should render icon if exists', () => { |
||||
const wrapper = setup({ |
||||
child: { |
||||
divider: false, |
||||
icon: 'icon-test', |
||||
}, |
||||
}); |
||||
|
||||
expect(wrapper).toMatchSnapshot(); |
||||
}); |
||||
}); |
||||
@ -0,0 +1,21 @@ |
||||
import React, { SFC } from 'react'; |
||||
|
||||
export interface Props { |
||||
child: any; |
||||
} |
||||
|
||||
const DropDownChild: SFC<Props> = props => { |
||||
const { child } = props; |
||||
const listItemClassName = child.divider ? 'divider' : ''; |
||||
|
||||
return ( |
||||
<li className={listItemClassName}> |
||||
<a href={child.url}> |
||||
{child.icon && <i className={child.icon} />} |
||||
{child.text} |
||||
</a> |
||||
</li> |
||||
); |
||||
}; |
||||
|
||||
export default DropDownChild; |
||||
@ -0,0 +1,70 @@ |
||||
import React from 'react'; |
||||
import { shallow } from 'enzyme'; |
||||
import { SideMenu } from './SideMenu'; |
||||
import appEvents from '../../app_events'; |
||||
import { contextSrv } from 'app/core/services/context_srv'; |
||||
|
||||
jest.mock('../../app_events', () => ({ |
||||
emit: jest.fn(), |
||||
})); |
||||
|
||||
jest.mock('app/core/services/context_srv', () => ({ |
||||
contextSrv: { |
||||
sidemenu: true, |
||||
user: {}, |
||||
isSignedIn: false, |
||||
isGrafanaAdmin: false, |
||||
isEditor: false, |
||||
hasEditPermissionFolders: false, |
||||
toggleSideMenu: jest.fn(), |
||||
}, |
||||
})); |
||||
|
||||
const setup = (propOverrides?: object) => { |
||||
const props = Object.assign( |
||||
{ |
||||
loginUrl: '', |
||||
user: {}, |
||||
mainLinks: [], |
||||
bottomeLinks: [], |
||||
isSignedIn: false, |
||||
}, |
||||
propOverrides |
||||
); |
||||
|
||||
return shallow(<SideMenu {...props} />); |
||||
}; |
||||
|
||||
describe('Render', () => { |
||||
it('should render component', () => { |
||||
const wrapper = setup(); |
||||
|
||||
expect(wrapper).toMatchSnapshot(); |
||||
}); |
||||
}); |
||||
|
||||
describe('Functions', () => { |
||||
describe('toggle side menu', () => { |
||||
const wrapper = setup(); |
||||
const instance = wrapper.instance() as SideMenu; |
||||
instance.toggleSideMenu(); |
||||
|
||||
it('should call contextSrv.toggleSideMenu', () => { |
||||
expect(contextSrv.toggleSideMenu).toHaveBeenCalled(); |
||||
}); |
||||
|
||||
it('should emit toggle sidemenu event', () => { |
||||
expect(appEvents.emit).toHaveBeenCalledWith('toggle-sidemenu'); |
||||
}); |
||||
}); |
||||
|
||||
describe('toggle side menu on mobile', () => { |
||||
const wrapper = setup(); |
||||
const instance = wrapper.instance() as SideMenu; |
||||
instance.toggleSideMenuSmallBreakpoint(); |
||||
|
||||
it('should emit toggle sidemenu event', () => { |
||||
expect(appEvents.emit).toHaveBeenCalledWith('toggle-sidemenu-mobile'); |
||||
}); |
||||
}); |
||||
}); |
||||
@ -0,0 +1,32 @@ |
||||
import React, { PureComponent } from 'react'; |
||||
import appEvents from '../../app_events'; |
||||
import { contextSrv } from 'app/core/services/context_srv'; |
||||
import TopSection from './TopSection'; |
||||
import BottomSection from './BottomSection'; |
||||
|
||||
export class SideMenu extends PureComponent { |
||||
toggleSideMenu = () => { |
||||
contextSrv.toggleSideMenu(); |
||||
appEvents.emit('toggle-sidemenu'); |
||||
}; |
||||
|
||||
toggleSideMenuSmallBreakpoint = () => { |
||||
appEvents.emit('toggle-sidemenu-mobile'); |
||||
}; |
||||
|
||||
render() { |
||||
return [ |
||||
<div className="sidemenu__logo" onClick={this.toggleSideMenu} key="logo"> |
||||
<img src="public/img/grafana_icon.svg" alt="graphana_logo" /> |
||||
</div>, |
||||
<div className="sidemenu__logo_small_breakpoint" onClick={this.toggleSideMenuSmallBreakpoint} key="hamburger"> |
||||
<i className="fa fa-bars" /> |
||||
<span className="sidemenu__close"> |
||||
<i className="fa fa-times" /> Close |
||||
</span> |
||||
</div>, |
||||
<TopSection key="topsection" />, |
||||
<BottomSection key="bottomsection" />, |
||||
]; |
||||
} |
||||
} |
||||
@ -0,0 +1,35 @@ |
||||
import React from 'react'; |
||||
import { shallow } from 'enzyme'; |
||||
import SideMenuDropDown from './SideMenuDropDown'; |
||||
|
||||
const setup = (propOverrides?: object) => { |
||||
const props = Object.assign( |
||||
{ |
||||
link: { |
||||
text: 'link', |
||||
}, |
||||
}, |
||||
propOverrides |
||||
); |
||||
|
||||
return shallow(<SideMenuDropDown {...props} />); |
||||
}; |
||||
|
||||
describe('Render', () => { |
||||
it('should render component', () => { |
||||
const wrapper = setup(); |
||||
|
||||
expect(wrapper).toMatchSnapshot(); |
||||
}); |
||||
|
||||
it('should render children', () => { |
||||
const wrapper = setup({ |
||||
link: { |
||||
text: 'link', |
||||
children: [{ id: 1 }, { id: 2 }, { id: 3 }], |
||||
}, |
||||
}); |
||||
|
||||
expect(wrapper).toMatchSnapshot(); |
||||
}); |
||||
}); |
||||
@ -0,0 +1,23 @@ |
||||
import React, { SFC } from 'react'; |
||||
import DropDownChild from './DropDownChild'; |
||||
|
||||
interface Props { |
||||
link: any; |
||||
} |
||||
|
||||
const SideMenuDropDown: SFC<Props> = props => { |
||||
const { link } = props; |
||||
return ( |
||||
<ul className="dropdown-menu dropdown-menu--sidemenu" role="menu"> |
||||
<li className="side-menu-header"> |
||||
<span className="sidemenu-item-text">{link.text}</span> |
||||
</li> |
||||
{link.children && |
||||
link.children.map((child, index) => { |
||||
return <DropDownChild child={child} key={`${child.url}-${index}`} />; |
||||
})} |
||||
</ul> |
||||
); |
||||
}; |
||||
|
||||
export default SideMenuDropDown; |
||||
@ -0,0 +1,11 @@ |
||||
import React from 'react'; |
||||
import { shallow } from 'enzyme'; |
||||
import SignIn from './SignIn'; |
||||
|
||||
describe('Render', () => { |
||||
it('should render component', () => { |
||||
const wrapper = shallow(<SignIn />); |
||||
|
||||
expect(wrapper).toMatchSnapshot(); |
||||
}); |
||||
}); |
||||
@ -0,0 +1,23 @@ |
||||
import React, { SFC } from 'react'; |
||||
|
||||
const SignIn: SFC<any> = () => { |
||||
const loginUrl = `login?redirect=${encodeURIComponent(window.location.pathname)}`; |
||||
return ( |
||||
<div className="sidemenu-item"> |
||||
<a href={loginUrl} className="sidemenu-link" target="_self"> |
||||
<span className="icon-circle sidemenu-icon"> |
||||
<i className="fa fa-fw fa-sign-in" /> |
||||
</span> |
||||
</a> |
||||
<a href={loginUrl} target="_self"> |
||||
<ul className="dropdown-menu dropdown-menu--sidemenu" role="menu"> |
||||
<li className="side-menu-header"> |
||||
<span className="sidemenu-item-text">Sign In</span> |
||||
</li> |
||||
</ul> |
||||
</a> |
||||
</div> |
||||
); |
||||
}; |
||||
|
||||
export default SignIn; |
||||
@ -0,0 +1,41 @@ |
||||
import React from 'react'; |
||||
import { shallow } from 'enzyme'; |
||||
import TopSection from './TopSection'; |
||||
|
||||
jest.mock('../../config', () => ({ |
||||
bootData: { |
||||
navTree: [ |
||||
{ id: '1', hideFromMenu: true }, |
||||
{ id: '2', hideFromMenu: true }, |
||||
{ id: '3', hideFromMenu: false }, |
||||
{ id: '4', hideFromMenu: true }, |
||||
], |
||||
}, |
||||
})); |
||||
|
||||
const setup = (propOverrides?: object) => { |
||||
const props = Object.assign( |
||||
{ |
||||
mainLinks: [], |
||||
}, |
||||
propOverrides |
||||
); |
||||
|
||||
return shallow(<TopSection {...props} />); |
||||
}; |
||||
|
||||
describe('Render', () => { |
||||
it('should render component', () => { |
||||
const wrapper = setup(); |
||||
|
||||
expect(wrapper).toMatchSnapshot(); |
||||
}); |
||||
|
||||
it('should render items', () => { |
||||
const wrapper = setup({ |
||||
mainLinks: [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }], |
||||
}); |
||||
|
||||
expect(wrapper).toMatchSnapshot(); |
||||
}); |
||||
}); |
||||
@ -0,0 +1,19 @@ |
||||
import React, { SFC } from 'react'; |
||||
import _ from 'lodash'; |
||||
import TopSectionItem from './TopSectionItem'; |
||||
import config from '../../config'; |
||||
|
||||
const TopSection: SFC<any> = () => { |
||||
const navTree = _.cloneDeep(config.bootData.navTree); |
||||
const mainLinks = _.filter(navTree, item => !item.hideFromMenu); |
||||
|
||||
return ( |
||||
<div className="sidemenu__top"> |
||||
{mainLinks.map((link, index) => { |
||||
return <TopSectionItem link={link} key={`${link.id}-${index}`} />; |
||||
})} |
||||
</div> |
||||
); |
||||
}; |
||||
|
||||
export default TopSection; |
||||
@ -0,0 +1,22 @@ |
||||
import React from 'react'; |
||||
import { shallow } from 'enzyme'; |
||||
import TopSectionItem from './TopSectionItem'; |
||||
|
||||
const setup = (propOverrides?: object) => { |
||||
const props = Object.assign( |
||||
{ |
||||
link: {}, |
||||
}, |
||||
propOverrides |
||||
); |
||||
|
||||
return shallow(<TopSectionItem {...props} />); |
||||
}; |
||||
|
||||
describe('Render', () => { |
||||
it('should render component', () => { |
||||
const wrapper = setup(); |
||||
|
||||
expect(wrapper).toMatchSnapshot(); |
||||
}); |
||||
}); |
||||
@ -0,0 +1,23 @@ |
||||
import React, { SFC } from 'react'; |
||||
import SideMenuDropDown from './SideMenuDropDown'; |
||||
|
||||
export interface Props { |
||||
link: any; |
||||
} |
||||
|
||||
const TopSectionItem: SFC<Props> = props => { |
||||
const { link } = props; |
||||
return ( |
||||
<div className="sidemenu-item dropdown"> |
||||
<a className="sidemenu-link" href={link.url} target={link.target}> |
||||
<span className="icon-circle sidemenu-icon"> |
||||
<i className={link.icon} /> |
||||
{link.img && <img src={link.img} />} |
||||
</span> |
||||
</a> |
||||
{link.children && <SideMenuDropDown link={link} />} |
||||
</div> |
||||
); |
||||
}; |
||||
|
||||
export default TopSectionItem; |
||||
@ -0,0 +1,163 @@ |
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP |
||||
|
||||
exports[`Render should render children 1`] = ` |
||||
<div |
||||
className="sidemenu-item dropdown dropup" |
||||
> |
||||
<a |
||||
className="sidemenu-link" |
||||
> |
||||
<span |
||||
className="icon-circle sidemenu-icon" |
||||
/> |
||||
</a> |
||||
<ul |
||||
className="dropdown-menu dropdown-menu--sidemenu" |
||||
role="menu" |
||||
> |
||||
<li |
||||
key="undefined-0" |
||||
> |
||||
<a |
||||
onClick={[Function]} |
||||
/> |
||||
</li> |
||||
<li |
||||
key="undefined-1" |
||||
> |
||||
<a |
||||
onClick={[Function]} |
||||
/> |
||||
</li> |
||||
<li |
||||
key="undefined-2" |
||||
> |
||||
<a |
||||
onClick={[Function]} |
||||
/> |
||||
</li> |
||||
<li |
||||
className="side-menu-header" |
||||
> |
||||
<span |
||||
className="sidemenu-item-text" |
||||
/> |
||||
</li> |
||||
</ul> |
||||
</div> |
||||
`; |
||||
|
||||
exports[`Render should render component 1`] = ` |
||||
<div |
||||
className="sidemenu-item dropdown dropup" |
||||
> |
||||
<a |
||||
className="sidemenu-link" |
||||
> |
||||
<span |
||||
className="icon-circle sidemenu-icon" |
||||
/> |
||||
</a> |
||||
<ul |
||||
className="dropdown-menu dropdown-menu--sidemenu" |
||||
role="menu" |
||||
> |
||||
<li |
||||
className="side-menu-header" |
||||
> |
||||
<span |
||||
className="sidemenu-item-text" |
||||
/> |
||||
</li> |
||||
</ul> |
||||
</div> |
||||
`; |
||||
|
||||
exports[`Render should render organisation switcher 1`] = ` |
||||
<div |
||||
className="sidemenu-item dropdown dropup" |
||||
> |
||||
<a |
||||
className="sidemenu-link" |
||||
> |
||||
<span |
||||
className="icon-circle sidemenu-icon" |
||||
/> |
||||
</a> |
||||
<ul |
||||
className="dropdown-menu dropdown-menu--sidemenu" |
||||
role="menu" |
||||
> |
||||
<li |
||||
className="sidemenu-org-switcher" |
||||
> |
||||
<a |
||||
onClick={[Function]} |
||||
> |
||||
<div> |
||||
<div |
||||
className="sidemenu-org-switcher__org-name" |
||||
> |
||||
Grafana |
||||
</div> |
||||
<div |
||||
className="sidemenu-org-switcher__org-current" |
||||
> |
||||
Current Org: |
||||
</div> |
||||
</div> |
||||
<div |
||||
className="sidemenu-org-switcher__switch" |
||||
> |
||||
<i |
||||
className="fa fa-fw fa-random" |
||||
/> |
||||
Switch |
||||
</div> |
||||
</a> |
||||
</li> |
||||
<li |
||||
className="side-menu-header" |
||||
> |
||||
<span |
||||
className="sidemenu-item-text" |
||||
/> |
||||
</li> |
||||
</ul> |
||||
</div> |
||||
`; |
||||
|
||||
exports[`Render should render subtitle 1`] = ` |
||||
<div |
||||
className="sidemenu-item dropdown dropup" |
||||
> |
||||
<a |
||||
className="sidemenu-link" |
||||
> |
||||
<span |
||||
className="icon-circle sidemenu-icon" |
||||
/> |
||||
</a> |
||||
<ul |
||||
className="dropdown-menu dropdown-menu--sidemenu" |
||||
role="menu" |
||||
> |
||||
<li |
||||
className="sidemenu-subtitle" |
||||
> |
||||
<span |
||||
className="sidemenu-item-text" |
||||
> |
||||
subtitle |
||||
</span> |
||||
</li> |
||||
<li |
||||
className="side-menu-header" |
||||
> |
||||
<span |
||||
className="sidemenu-item-text" |
||||
/> |
||||
</li> |
||||
</ul> |
||||
</div> |
||||
`; |
||||
@ -0,0 +1,34 @@ |
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP |
||||
|
||||
exports[`Render should render component 1`] = ` |
||||
<div |
||||
className="sidemenu__bottom" |
||||
> |
||||
<SignIn /> |
||||
<BottomNavLinks |
||||
key="undefined-0" |
||||
link={ |
||||
Object { |
||||
"hideFromMenu": true, |
||||
"id": "profile", |
||||
} |
||||
} |
||||
/> |
||||
<BottomNavLinks |
||||
key="undefined-1" |
||||
link={ |
||||
Object { |
||||
"hideFromMenu": true, |
||||
} |
||||
} |
||||
/> |
||||
<BottomNavLinks |
||||
key="undefined-2" |
||||
link={ |
||||
Object { |
||||
"hideFromMenu": true, |
||||
} |
||||
} |
||||
/> |
||||
</div> |
||||
`; |
||||
@ -0,0 +1,21 @@ |
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP |
||||
|
||||
exports[`Render should render component 1`] = ` |
||||
<li |
||||
className="divider" |
||||
> |
||||
<a /> |
||||
</li> |
||||
`; |
||||
|
||||
exports[`Render should render icon if exists 1`] = ` |
||||
<li |
||||
className="" |
||||
> |
||||
<a> |
||||
<i |
||||
className="icon-test" |
||||
/> |
||||
</a> |
||||
</li> |
||||
`; |
||||
@ -0,0 +1,22 @@ |
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP |
||||
|
||||
exports[`Render should render component 1`] = ` |
||||
Array [ |
||||
<div |
||||
className="sidemenu__logo" |
||||
key="logo" |
||||
onClick={[Function]} |
||||
/>, |
||||
<div |
||||
className="sidemenu__logo_small_breakpoint" |
||||
key="hamburger" |
||||
onClick={[Function]} |
||||
/>, |
||||
<TopSection |
||||
key="topsection" |
||||
/>, |
||||
<BottomSection |
||||
key="bottomsection" |
||||
/>, |
||||
] |
||||
`; |
||||
@ -0,0 +1,59 @@ |
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP |
||||
|
||||
exports[`Render should render children 1`] = ` |
||||
<ul |
||||
className="dropdown-menu dropdown-menu--sidemenu" |
||||
role="menu" |
||||
> |
||||
<li |
||||
className="side-menu-header" |
||||
> |
||||
<span |
||||
className="sidemenu-item-text" |
||||
> |
||||
link |
||||
</span> |
||||
</li> |
||||
<DropDownChild |
||||
child={ |
||||
Object { |
||||
"id": 1, |
||||
} |
||||
} |
||||
key="undefined-0" |
||||
/> |
||||
<DropDownChild |
||||
child={ |
||||
Object { |
||||
"id": 2, |
||||
} |
||||
} |
||||
key="undefined-1" |
||||
/> |
||||
<DropDownChild |
||||
child={ |
||||
Object { |
||||
"id": 3, |
||||
} |
||||
} |
||||
key="undefined-2" |
||||
/> |
||||
</ul> |
||||
`; |
||||
|
||||
exports[`Render should render component 1`] = ` |
||||
<ul |
||||
className="dropdown-menu dropdown-menu--sidemenu" |
||||
role="menu" |
||||
> |
||||
<li |
||||
className="side-menu-header" |
||||
> |
||||
<span |
||||
className="sidemenu-item-text" |
||||
> |
||||
link |
||||
</span> |
||||
</li> |
||||
</ul> |
||||
`; |
||||
@ -0,0 +1,40 @@ |
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP |
||||
|
||||
exports[`Render should render component 1`] = ` |
||||
<div |
||||
className="sidemenu-item" |
||||
> |
||||
<a |
||||
className="sidemenu-link" |
||||
href="login?redirect=blank" |
||||
target="_self" |
||||
> |
||||
<span |
||||
className="icon-circle sidemenu-icon" |
||||
> |
||||
<i |
||||
className="fa fa-fw fa-sign-in" |
||||
/> |
||||
</span> |
||||
</a> |
||||
<a |
||||
href="login?redirect=blank" |
||||
target="_self" |
||||
> |
||||
<ul |
||||
className="dropdown-menu dropdown-menu--sidemenu" |
||||
role="menu" |
||||
> |
||||
<li |
||||
className="side-menu-header" |
||||
> |
||||
<span |
||||
className="sidemenu-item-text" |
||||
> |
||||
Sign In |
||||
</span> |
||||
</li> |
||||
</ul> |
||||
</a> |
||||
</div> |
||||
`; |
||||
@ -0,0 +1,33 @@ |
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP |
||||
|
||||
exports[`Render should render component 1`] = ` |
||||
<div |
||||
className="sidemenu__top" |
||||
> |
||||
<TopSectionItem |
||||
key="3-0" |
||||
link={ |
||||
Object { |
||||
"hideFromMenu": false, |
||||
"id": "3", |
||||
} |
||||
} |
||||
/> |
||||
</div> |
||||
`; |
||||
|
||||
exports[`Render should render items 1`] = ` |
||||
<div |
||||
className="sidemenu__top" |
||||
> |
||||
<TopSectionItem |
||||
key="3-0" |
||||
link={ |
||||
Object { |
||||
"hideFromMenu": false, |
||||
"id": "3", |
||||
} |
||||
} |
||||
/> |
||||
</div> |
||||
`; |
||||
@ -0,0 +1,17 @@ |
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP |
||||
|
||||
exports[`Render should render component 1`] = ` |
||||
<div |
||||
className="sidemenu-item dropdown" |
||||
> |
||||
<a |
||||
className="sidemenu-link" |
||||
> |
||||
<span |
||||
className="icon-circle sidemenu-icon" |
||||
> |
||||
<i /> |
||||
</span> |
||||
</a> |
||||
</div> |
||||
`; |
||||
@ -1,81 +0,0 @@ |
||||
<a class="sidemenu__logo" ng-click="ctrl.toggleSideMenu()"> |
||||
<img src="public/img/grafana_icon.svg"></img> |
||||
</a> |
||||
|
||||
<a class="sidemenu__logo_small_breakpoint" ng-click="ctrl.toggleSideMenuSmallBreakpoint()"> |
||||
<i class="fa fa-bars"></i> |
||||
<span class="sidemenu__close"> |
||||
<i class="fa fa-times"></i> Close</span> |
||||
</a> |
||||
|
||||
<div class="sidemenu__top"> |
||||
<div ng-repeat="item in ::ctrl.mainLinks" class="sidemenu-item dropdown"> |
||||
<a href="{{::item.url}}" class="sidemenu-link" target="{{::item.target}}"> |
||||
<span class="icon-circle sidemenu-icon"> |
||||
<i class="{{::item.icon}}" ng-show="::item.icon"></i> |
||||
<img ng-src="{{::item.img}}" ng-show="::item.img"> |
||||
</span> |
||||
</a> |
||||
<ul class="dropdown-menu dropdown-menu--sidemenu" role="menu" ng-if="::item.children"> |
||||
<li class="side-menu-header"> |
||||
<span class="sidemenu-item-text">{{::item.text}}</span> |
||||
</li> |
||||
<li ng-repeat="child in ::item.children" ng-class="{divider: child.divider}"> |
||||
<a href="{{::child.url}}"> |
||||
<i class="{{::child.icon}}" ng-show="::child.icon"></i> |
||||
{{::child.text}} |
||||
</a> |
||||
</li> |
||||
</ul> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="sidemenu__bottom"> |
||||
<div ng-show="::!ctrl.isSignedIn" class="sidemenu-item"> |
||||
<a href="{{ctrl.loginUrl}}" class="sidemenu-link" target="_self"> |
||||
<span class="icon-circle sidemenu-icon"> |
||||
<i class="fa fa-fw fa-sign-in"></i> |
||||
</span> |
||||
</a> |
||||
<a href="{{ctrl.loginUrl}}" target="_self"> |
||||
<ul class="dropdown-menu dropdown-menu--sidemenu" role="menu"> |
||||
<li class="side-menu-header"> |
||||
<span class="sidemenu-item-text">Sign In</span> |
||||
</li> |
||||
</ul> |
||||
</a> |
||||
</div> |
||||
|
||||
<div ng-repeat="item in ::ctrl.bottomNav" class="sidemenu-item dropdown dropup"> |
||||
<a href="{{::item.url}}" class="sidemenu-link" target="{{::item.target}}"> |
||||
<span class="icon-circle sidemenu-icon"> |
||||
<i class="{{::item.icon}}" ng-show="::item.icon"></i> |
||||
<img ng-src="{{::item.img}}" ng-show="::item.img"> |
||||
</span> |
||||
</a> |
||||
<ul class="dropdown-menu dropdown-menu--sidemenu" role="menu"> |
||||
<li ng-if="item.subTitle" class="sidemenu-subtitle"> |
||||
<span class="sidemenu-item-text">{{::item.subTitle}}</span> |
||||
</li> |
||||
<li ng-if="item.showOrgSwitcher" class="sidemenu-org-switcher"> |
||||
<a ng-click="ctrl.switchOrg()"> |
||||
<div> |
||||
<div class="sidemenu-org-switcher__org-name">{{ctrl.contextSrv.user.orgName}}</div> |
||||
<div class="sidemenu-org-switcher__org-current">Current Org:</div> |
||||
</div> |
||||
<div class="sidemenu-org-switcher__switch"> |
||||
<i class="fa fa-fw fa-random"></i>Switch</div> |
||||
</a> |
||||
</li> |
||||
<li ng-repeat="child in ::item.children" ng-class="{divider: child.divider}" ng-hide="::child.hideFromMenu"> |
||||
<a href="{{::child.url}}" target="{{::child.target}}" ng-click="ctrl.itemClicked(child, $event)"> |
||||
<i class="{{::child.icon}}" ng-show="::child.icon"></i> |
||||
{{::child.text}} |
||||
</a> |
||||
</li> |
||||
<li class="side-menu-header"> |
||||
<span class="sidemenu-item-text">{{::item.text}}</span> |
||||
</li> |
||||
</ul> |
||||
</div> |
||||
</div> |
||||
@ -1,89 +0,0 @@ |
||||
import _ from 'lodash'; |
||||
import config from 'app/core/config'; |
||||
import $ from 'jquery'; |
||||
import coreModule from '../../core_module'; |
||||
import appEvents from 'app/core/app_events'; |
||||
|
||||
export class SideMenuCtrl { |
||||
user: any; |
||||
mainLinks: any; |
||||
bottomNav: any; |
||||
loginUrl: string; |
||||
isSignedIn: boolean; |
||||
isOpenMobile: boolean; |
||||
|
||||
/** @ngInject */ |
||||
constructor(private $scope, private $rootScope, private $location, private contextSrv, private $timeout) { |
||||
this.isSignedIn = contextSrv.isSignedIn; |
||||
this.user = contextSrv.user; |
||||
|
||||
const navTree = _.cloneDeep(config.bootData.navTree); |
||||
this.mainLinks = _.filter(navTree, item => !item.hideFromMenu); |
||||
this.bottomNav = _.filter(navTree, item => item.hideFromMenu); |
||||
this.loginUrl = 'login?redirect=' + encodeURIComponent(this.$location.path()); |
||||
|
||||
if (contextSrv.user.orgCount > 1) { |
||||
const profileNode = _.find(this.bottomNav, { id: 'profile' }); |
||||
if (profileNode) { |
||||
profileNode.showOrgSwitcher = true; |
||||
} |
||||
} |
||||
|
||||
this.$scope.$on('$routeChangeSuccess', () => { |
||||
this.loginUrl = 'login?redirect=' + encodeURIComponent(this.$location.path()); |
||||
}); |
||||
} |
||||
|
||||
toggleSideMenu() { |
||||
this.contextSrv.toggleSideMenu(); |
||||
appEvents.emit('toggle-sidemenu'); |
||||
|
||||
this.$timeout(() => { |
||||
this.$rootScope.$broadcast('render'); |
||||
}); |
||||
} |
||||
|
||||
toggleSideMenuSmallBreakpoint() { |
||||
appEvents.emit('toggle-sidemenu-mobile'); |
||||
} |
||||
|
||||
switchOrg() { |
||||
this.$rootScope.appEvent('show-modal', { |
||||
templateHtml: '<org-switcher dismiss="dismiss()"></org-switcher>', |
||||
}); |
||||
} |
||||
|
||||
itemClicked(item, evt) { |
||||
if (item.url === '/shortcuts') { |
||||
appEvents.emit('show-modal', { |
||||
templateHtml: '<help-modal></help-modal>', |
||||
}); |
||||
evt.preventDefault(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
export function sideMenuDirective() { |
||||
return { |
||||
restrict: 'E', |
||||
templateUrl: 'public/app/core/components/sidemenu/sidemenu.html', |
||||
controller: SideMenuCtrl, |
||||
bindToController: true, |
||||
controllerAs: 'ctrl', |
||||
scope: {}, |
||||
link: function(scope, elem) { |
||||
// hack to hide dropdown menu
|
||||
elem.on('click.dropdown', '.dropdown-menu a', function(evt) { |
||||
const menu = $(evt.target).parents('.dropdown-menu'); |
||||
const parent = menu.parent(); |
||||
menu.detach(); |
||||
|
||||
setTimeout(function() { |
||||
parent.append(menu); |
||||
}, 100); |
||||
}); |
||||
}, |
||||
}; |
||||
} |
||||
|
||||
coreModule.directive('sidemenu', sideMenuDirective); |
||||
Loading…
Reference in new issue