Commit cab6861d by Peter Holmberg Committed by Torkel Ödegaard

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
parent 0f326f18
...@@ -4,10 +4,12 @@ import PageHeader from './components/PageHeader/PageHeader'; ...@@ -4,10 +4,12 @@ import PageHeader from './components/PageHeader/PageHeader';
import EmptyListCTA from './components/EmptyListCTA/EmptyListCTA'; import EmptyListCTA from './components/EmptyListCTA/EmptyListCTA';
import { SearchResult } from './components/search/SearchResult'; import { SearchResult } from './components/search/SearchResult';
import { TagFilter } from './components/TagFilter/TagFilter'; import { TagFilter } from './components/TagFilter/TagFilter';
import { SideMenu } from './components/sidemenu/SideMenu';
import DashboardPermissions from './components/Permissions/DashboardPermissions'; import DashboardPermissions from './components/Permissions/DashboardPermissions';
export function registerAngularDirectives() { export function registerAngularDirectives() {
react2AngularDirective('passwordStrength', PasswordStrength, ['password']); react2AngularDirective('passwordStrength', PasswordStrength, ['password']);
react2AngularDirective('sidemenu', SideMenu, []);
react2AngularDirective('pageHeader', PageHeader, ['model', 'noTabs']); react2AngularDirective('pageHeader', PageHeader, ['model', 'noTabs']);
react2AngularDirective('emptyListCta', EmptyListCTA, ['model']); react2AngularDirective('emptyListCta', EmptyListCTA, ['model']);
react2AngularDirective('searchResult', SearchResult, []); react2AngularDirective('searchResult', SearchResult, []);
......
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>' });
});
});
});
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;
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();
});
});
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>
);
}
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();
});
});
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;
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');
});
});
});
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" />&nbsp;Close
</span>
</div>,
<TopSection key="topsection" />,
<BottomSection key="bottomsection" />,
];
}
}
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();
});
});
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;
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();
});
});
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;
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();
});
});
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;
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();
});
});
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;
// 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>
`;
// 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>
`;
// 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>
`;
// 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"
/>,
]
`;
// 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>
`;
// 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>
`;
// 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>
`;
// 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>
`;
<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>&nbsp;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>
\ No newline at end of file
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'; ...@@ -20,7 +20,6 @@ import './services/search_srv';
import './services/ng_react'; import './services/ng_react';
import { grafanaAppDirective } from './components/grafana_app'; import { grafanaAppDirective } from './components/grafana_app';
import { sideMenuDirective } from './components/sidemenu/sidemenu';
import { searchDirective } from './components/search/search'; import { searchDirective } from './components/search/search';
import { infoPopover } from './components/info_popover'; import { infoPopover } from './components/info_popover';
import { navbarDirective } from './components/navbar/navbar'; import { navbarDirective } from './components/navbar/navbar';
...@@ -62,7 +61,6 @@ export { ...@@ -62,7 +61,6 @@ export {
arrayJoin, arrayJoin,
coreModule, coreModule,
grafanaAppDirective, grafanaAppDirective,
sideMenuDirective,
navbarDirective, navbarDirective,
searchDirective, searchDirective,
liveSrv, liveSrv,
......
...@@ -8,6 +8,8 @@ export class User { ...@@ -8,6 +8,8 @@ export class User {
isSignedIn: any; isSignedIn: any;
orgRole: any; orgRole: any;
orgId: number; orgId: number;
orgName: string;
orgCount: number;
timezone: string; timezone: string;
helpFlags1: number; helpFlags1: number;
lightTheme: boolean; lightTheme: boolean;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment