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 logging
This commit is contained in:
committed by
Torkel Ödegaard
parent
0f326f18dc
commit
cab6861d27
@@ -4,10 +4,12 @@ import PageHeader from './components/PageHeader/PageHeader';
|
||||
import EmptyListCTA from './components/EmptyListCTA/EmptyListCTA';
|
||||
import { SearchResult } from './components/search/SearchResult';
|
||||
import { TagFilter } from './components/TagFilter/TagFilter';
|
||||
import { SideMenu } from './components/sidemenu/SideMenu';
|
||||
import DashboardPermissions from './components/Permissions/DashboardPermissions';
|
||||
|
||||
export function registerAngularDirectives() {
|
||||
react2AngularDirective('passwordStrength', PasswordStrength, ['password']);
|
||||
react2AngularDirective('sidemenu', SideMenu, []);
|
||||
react2AngularDirective('pageHeader', PageHeader, ['model', 'noTabs']);
|
||||
react2AngularDirective('emptyListCta', EmptyListCTA, ['model']);
|
||||
react2AngularDirective('searchResult', SearchResult, []);
|
||||
|
||||
@@ -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);
|
||||
@@ -20,7 +20,6 @@ import './services/search_srv';
|
||||
import './services/ng_react';
|
||||
|
||||
import { grafanaAppDirective } from './components/grafana_app';
|
||||
import { sideMenuDirective } from './components/sidemenu/sidemenu';
|
||||
import { searchDirective } from './components/search/search';
|
||||
import { infoPopover } from './components/info_popover';
|
||||
import { navbarDirective } from './components/navbar/navbar';
|
||||
@@ -62,7 +61,6 @@ export {
|
||||
arrayJoin,
|
||||
coreModule,
|
||||
grafanaAppDirective,
|
||||
sideMenuDirective,
|
||||
navbarDirective,
|
||||
searchDirective,
|
||||
liveSrv,
|
||||
|
||||
@@ -8,6 +8,8 @@ export class User {
|
||||
isSignedIn: any;
|
||||
orgRole: any;
|
||||
orgId: number;
|
||||
orgName: string;
|
||||
orgCount: number;
|
||||
timezone: string;
|
||||
helpFlags1: number;
|
||||
lightTheme: boolean;
|
||||
|
||||
Reference in New Issue
Block a user