mirror of
https://github.com/grafana/grafana.git
synced 2025-02-10 23:55:47 -06:00
Migration: Migrate org switcher to react (#19607)
* Migration: Migrate org switcher to react * Improve modal overflow behavior * Updated modal backdrop * Renamed type * Modal: Refactoring and reducing duplication
This commit is contained in:
parent
5cd4ffffe3
commit
a093fbb51a
@ -18,6 +18,7 @@ export * from './datasource';
|
|||||||
export * from './panel';
|
export * from './panel';
|
||||||
export * from './plugin';
|
export * from './plugin';
|
||||||
export * from './theme';
|
export * from './theme';
|
||||||
|
export * from './orgs';
|
||||||
|
|
||||||
import * as AppEvents from './appEvents';
|
import * as AppEvents from './appEvents';
|
||||||
import { AppEvent } from './appEvents';
|
import { AppEvent } from './appEvents';
|
||||||
|
11
packages/grafana-data/src/types/orgs.ts
Normal file
11
packages/grafana-data/src/types/orgs.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
export interface UserOrgDTO {
|
||||||
|
orgId: number;
|
||||||
|
name: string;
|
||||||
|
role: OrgRole;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum OrgRole {
|
||||||
|
Admin = 'Admin',
|
||||||
|
Editor = 'Editor',
|
||||||
|
Viewer = 'Viewer',
|
||||||
|
}
|
@ -8,13 +8,12 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => ({
|
|||||||
modal: css`
|
modal: css`
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: ${theme.zIndex.modal};
|
z-index: ${theme.zIndex.modal};
|
||||||
width: 100%;
|
|
||||||
background: ${theme.colors.pageBg};
|
background: ${theme.colors.pageBg};
|
||||||
box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
|
box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
|
||||||
background-clip: padding-box;
|
background-clip: padding-box;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
width: 750px;
|
||||||
max-width: 750px;
|
max-width: 100%;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
@ -28,20 +27,25 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => ({
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
z-index: ${theme.zIndex.modalBackdrop};
|
z-index: ${theme.zIndex.modalBackdrop};
|
||||||
background-color: ${theme.colors.bodyBg};
|
background-color: ${theme.colors.blueFaint};
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
backdrop-filter: blur(4px);
|
backdrop-filter: blur(4px);
|
||||||
`,
|
`,
|
||||||
modalHeader: css`
|
modalHeader: css`
|
||||||
background: ${theme.background.pageHeader};
|
background: ${theme.background.pageHeader};
|
||||||
box-shadow: ${theme.shadow.pageHeader};
|
box-shadow: ${theme.shadow.pageHeader};
|
||||||
border-bottom: 1px soliod ${theme.colors.pageHeaderBorder};
|
border-bottom: 1px solid ${theme.colors.pageHeaderBorder};
|
||||||
display: flex;
|
display: flex;
|
||||||
`,
|
`,
|
||||||
modalHeaderTitle: css`
|
modalHeaderTitle: css`
|
||||||
font-size: ${theme.typography.heading.h3};
|
font-size: ${theme.typography.heading.h3};
|
||||||
padding-top: calc(${theme.spacing.d} * 0.75);
|
padding-top: ${theme.spacing.sm};
|
||||||
margin: 0 calc(${theme.spacing.d} * 3) 0 calc(${theme.spacing.d} * 1.5);
|
margin: 0 ${theme.spacing.md};
|
||||||
|
`,
|
||||||
|
modalHeaderIcon: css`
|
||||||
|
position: relative;
|
||||||
|
top: 2px;
|
||||||
|
padding-right: ${theme.spacing.md};
|
||||||
`,
|
`,
|
||||||
modalHeaderClose: css`
|
modalHeaderClose: css`
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
@ -49,10 +53,14 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => ({
|
|||||||
`,
|
`,
|
||||||
modalContent: css`
|
modalContent: css`
|
||||||
padding: calc(${theme.spacing.d} * 2);
|
padding: calc(${theme.spacing.d} * 2);
|
||||||
|
overflow: auto;
|
||||||
|
width: 100%;
|
||||||
|
max-height: calc(90vh - ${theme.spacing.d} * 2);
|
||||||
`,
|
`,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
icon?: string;
|
||||||
title: string | JSX.Element;
|
title: string | JSX.Element;
|
||||||
theme: GrafanaTheme;
|
theme: GrafanaTheme;
|
||||||
|
|
||||||
@ -74,6 +82,18 @@ export class UnthemedModal extends React.PureComponent<Props> {
|
|||||||
this.onDismiss();
|
this.onDismiss();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
renderDefaultHeader() {
|
||||||
|
const { title, icon, theme } = this.props;
|
||||||
|
const styles = getStyles(theme);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<h2 className={styles.modalHeaderTitle}>
|
||||||
|
{icon && <i className={cx(icon, styles.modalHeaderIcon)} />}
|
||||||
|
{title}
|
||||||
|
</h2>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { title, isOpen = false, theme } = this.props;
|
const { title, isOpen = false, theme } = this.props;
|
||||||
const styles = getStyles(theme);
|
const styles = getStyles(theme);
|
||||||
@ -84,16 +104,16 @@ export class UnthemedModal extends React.PureComponent<Props> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Portal>
|
<Portal>
|
||||||
<div className={cx(styles.modal)}>
|
<div className={styles.modal}>
|
||||||
<div className={cx(styles.modalHeader)}>
|
<div className={styles.modalHeader}>
|
||||||
{typeof title === 'string' ? <h2 className={cx(styles.modalHeaderTitle)}>{title}</h2> : <>{title}</>}
|
{typeof title === 'string' ? this.renderDefaultHeader() : title}
|
||||||
<a className={cx(styles.modalHeaderClose)} onClick={this.onDismiss}>
|
<a className={styles.modalHeaderClose} onClick={this.onDismiss}>
|
||||||
<i className="fa fa-remove" />
|
<i className="fa fa-remove" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div className={cx(styles.modalContent)}>{this.props.children}</div>
|
<div className={styles.modalContent}>{this.props.children}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={cx(styles.modalBackdrop)} onClick={this.props.onClickBackdrop || this.onClickBackdrop} />
|
<div className={styles.modalBackdrop} onClick={this.props.onClickBackdrop || this.onClickBackdrop} />
|
||||||
</Portal>
|
</Portal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
81
public/app/core/components/OrgSwitcher.tsx
Normal file
81
public/app/core/components/OrgSwitcher.tsx
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { getBackendSrv } from '@grafana/runtime';
|
||||||
|
import { UserOrgDTO } from '@grafana/data';
|
||||||
|
import { Modal, Button } from '@grafana/ui';
|
||||||
|
|
||||||
|
import { contextSrv } from 'app/core/services/context_srv';
|
||||||
|
import config from 'app/core/config';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
onDismiss: () => void;
|
||||||
|
isOpen: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
orgs: UserOrgDTO[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class OrgSwitcher extends React.PureComponent<Props, State> {
|
||||||
|
state: State = {
|
||||||
|
orgs: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.getUserOrgs();
|
||||||
|
}
|
||||||
|
|
||||||
|
getUserOrgs = async () => {
|
||||||
|
const orgs: UserOrgDTO[] = await getBackendSrv().get('/api/user/orgs');
|
||||||
|
this.setState({
|
||||||
|
orgs: orgs.sort((a, b) => a.orgId - b.orgId),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
setCurrentOrg = async (org: UserOrgDTO) => {
|
||||||
|
await getBackendSrv().post(`/api/user/using/${org.orgId}`);
|
||||||
|
this.setWindowLocation(`${config.appSubUrl}${config.appSubUrl.endsWith('/') ? '' : '/'}?orgId=${org.orgId}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
setWindowLocation(href: string) {
|
||||||
|
window.location.href = href;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { onDismiss, isOpen } = this.props;
|
||||||
|
const { orgs } = this.state;
|
||||||
|
|
||||||
|
const currentOrgId = contextSrv.user.orgId;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal title="Switch Organization" icon="fa fa-random" onDismiss={onDismiss} isOpen={isOpen}>
|
||||||
|
<table className="filter-table form-inline">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Role</th>
|
||||||
|
<th />
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{orgs.map(org => (
|
||||||
|
<tr key={org.orgId}>
|
||||||
|
<td>{org.name}</td>
|
||||||
|
<td>{org.role}</td>
|
||||||
|
<td className="text-right">
|
||||||
|
{org.orgId === currentOrgId ? (
|
||||||
|
<Button size="sm">Current</Button>
|
||||||
|
) : (
|
||||||
|
<Button variant="inverse" size="sm" onClick={() => this.setCurrentOrg(org)}>
|
||||||
|
Switch to
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,84 +0,0 @@
|
|||||||
import coreModule from 'app/core/core_module';
|
|
||||||
import { contextSrv } from 'app/core/services/context_srv';
|
|
||||||
import config from 'app/core/config';
|
|
||||||
import { BackendSrv } from '../services/backend_srv';
|
|
||||||
|
|
||||||
const template = `
|
|
||||||
<div class="modal-body">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h2 class="modal-header-title">
|
|
||||||
<i class="fa fa-random"></i>
|
|
||||||
<span class="p-l-1">Switch Organization</span>
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<a class="modal-header-close" ng-click="ctrl.dismiss();">
|
|
||||||
<i class="fa fa-remove"></i>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="modal-content modal-content--has-scroll" grafana-scrollbar>
|
|
||||||
<table class="filter-table form-inline">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Name</th>
|
|
||||||
<th>Role</th>
|
|
||||||
<th></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr ng-repeat="org in ctrl.orgs">
|
|
||||||
<td>{{org.name}}</td>
|
|
||||||
<td>{{org.role}}</td>
|
|
||||||
<td class="text-right">
|
|
||||||
<span class="btn btn-primary btn-small" ng-show="org.orgId === ctrl.currentOrgId">
|
|
||||||
Current
|
|
||||||
</span>
|
|
||||||
<a ng-click="ctrl.setUsingOrg(org)" class="btn btn-inverse btn-small" ng-show="org.orgId !== ctrl.currentOrgId">
|
|
||||||
Switch to
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
export class OrgSwitchCtrl {
|
|
||||||
orgs: any[];
|
|
||||||
currentOrgId: any;
|
|
||||||
|
|
||||||
/** @ngInject */
|
|
||||||
constructor(private backendSrv: BackendSrv) {
|
|
||||||
this.currentOrgId = contextSrv.user.orgId;
|
|
||||||
this.getUserOrgs();
|
|
||||||
}
|
|
||||||
|
|
||||||
getUserOrgs() {
|
|
||||||
this.backendSrv.get('/api/user/orgs').then((orgs: any) => {
|
|
||||||
this.orgs = orgs;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setUsingOrg(org: any) {
|
|
||||||
return this.backendSrv.post('/api/user/using/' + org.orgId).then(() => {
|
|
||||||
this.setWindowLocation(config.appSubUrl + (config.appSubUrl.endsWith('/') ? '' : '/') + '?orgId=' + org.orgId);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setWindowLocation(href: string) {
|
|
||||||
window.location.href = href;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function orgSwitcher() {
|
|
||||||
return {
|
|
||||||
restrict: 'E',
|
|
||||||
template: template,
|
|
||||||
controller: OrgSwitchCtrl,
|
|
||||||
bindToController: true,
|
|
||||||
controllerAs: 'ctrl',
|
|
||||||
scope: { dismiss: '&' },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
coreModule.directive('orgSwitcher', orgSwitcher);
|
|
@ -3,13 +3,22 @@ import appEvents from '../../app_events';
|
|||||||
import { User } from '../../services/context_srv';
|
import { User } from '../../services/context_srv';
|
||||||
import { NavModelItem } from '@grafana/data';
|
import { NavModelItem } from '@grafana/data';
|
||||||
import { CoreEvents } from 'app/types';
|
import { CoreEvents } from 'app/types';
|
||||||
|
import { OrgSwitcher } from '../OrgSwitcher';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
link: NavModelItem;
|
link: NavModelItem;
|
||||||
user: User;
|
user: User;
|
||||||
}
|
}
|
||||||
|
|
||||||
class BottomNavLinks extends PureComponent<Props> {
|
interface State {
|
||||||
|
showSwitcherModal: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
class BottomNavLinks extends PureComponent<Props, State> {
|
||||||
|
state: State = {
|
||||||
|
showSwitcherModal: false,
|
||||||
|
};
|
||||||
|
|
||||||
itemClicked = (event: React.SyntheticEvent, child: NavModelItem) => {
|
itemClicked = (event: React.SyntheticEvent, child: NavModelItem) => {
|
||||||
if (child.url === '/shortcuts') {
|
if (child.url === '/shortcuts') {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@ -19,14 +28,16 @@ class BottomNavLinks extends PureComponent<Props> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
switchOrg = () => {
|
toggleSwitcherModal = () => {
|
||||||
appEvents.emit(CoreEvents.showModal, {
|
this.setState(prevState => ({
|
||||||
templateHtml: '<org-switcher dismiss="dismiss()"></org-switcher>',
|
showSwitcherModal: !prevState.showSwitcherModal,
|
||||||
});
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { link, user } = this.props;
|
const { link, user } = this.props;
|
||||||
|
const { showSwitcherModal } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="sidemenu-item dropdown dropup">
|
<div className="sidemenu-item dropdown dropup">
|
||||||
<a href={link.url} className="sidemenu-link" target={link.target}>
|
<a href={link.url} className="sidemenu-link" target={link.target}>
|
||||||
@ -43,7 +54,7 @@ class BottomNavLinks extends PureComponent<Props> {
|
|||||||
)}
|
)}
|
||||||
{link.showOrgSwitcher && (
|
{link.showOrgSwitcher && (
|
||||||
<li className="sidemenu-org-switcher">
|
<li className="sidemenu-org-switcher">
|
||||||
<a onClick={this.switchOrg}>
|
<a onClick={this.toggleSwitcherModal}>
|
||||||
<div>
|
<div>
|
||||||
<div className="sidemenu-org-switcher__org-name">{user.orgName}</div>
|
<div className="sidemenu-org-switcher__org-name">{user.orgName}</div>
|
||||||
<div className="sidemenu-org-switcher__org-current">Current Org:</div>
|
<div className="sidemenu-org-switcher__org-current">Current Org:</div>
|
||||||
@ -55,6 +66,9 @@ class BottomNavLinks extends PureComponent<Props> {
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<OrgSwitcher onDismiss={this.toggleSwitcherModal} isOpen={showSwitcherModal} />
|
||||||
|
|
||||||
{link.children &&
|
{link.children &&
|
||||||
link.children.map((child, index) => {
|
link.children.map((child, index) => {
|
||||||
if (!child.hideFromMenu) {
|
if (!child.hideFromMenu) {
|
||||||
|
@ -15,6 +15,10 @@ exports[`Render should render children 1`] = `
|
|||||||
className="dropdown-menu dropdown-menu--sidemenu"
|
className="dropdown-menu dropdown-menu--sidemenu"
|
||||||
role="menu"
|
role="menu"
|
||||||
>
|
>
|
||||||
|
<OrgSwitcher
|
||||||
|
isOpen={false}
|
||||||
|
onDismiss={[Function]}
|
||||||
|
/>
|
||||||
<li
|
<li
|
||||||
key="undefined-0"
|
key="undefined-0"
|
||||||
>
|
>
|
||||||
@ -62,6 +66,10 @@ exports[`Render should render component 1`] = `
|
|||||||
className="dropdown-menu dropdown-menu--sidemenu"
|
className="dropdown-menu dropdown-menu--sidemenu"
|
||||||
role="menu"
|
role="menu"
|
||||||
>
|
>
|
||||||
|
<OrgSwitcher
|
||||||
|
isOpen={false}
|
||||||
|
onDismiss={[Function]}
|
||||||
|
/>
|
||||||
<li
|
<li
|
||||||
className="side-menu-header"
|
className="side-menu-header"
|
||||||
>
|
>
|
||||||
@ -118,6 +126,10 @@ exports[`Render should render organization switcher 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<OrgSwitcher
|
||||||
|
isOpen={false}
|
||||||
|
onDismiss={[Function]}
|
||||||
|
/>
|
||||||
<li
|
<li
|
||||||
className="side-menu-header"
|
className="side-menu-header"
|
||||||
>
|
>
|
||||||
@ -153,6 +165,10 @@ exports[`Render should render subtitle 1`] = `
|
|||||||
subtitle
|
subtitle
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
|
<OrgSwitcher
|
||||||
|
isOpen={false}
|
||||||
|
onDismiss={[Function]}
|
||||||
|
/>
|
||||||
<li
|
<li
|
||||||
className="side-menu-header"
|
className="side-menu-header"
|
||||||
>
|
>
|
||||||
|
@ -39,7 +39,6 @@ import { contextSrv } from './services/context_srv';
|
|||||||
import { KeybindingSrv } from './services/keybindingSrv';
|
import { KeybindingSrv } from './services/keybindingSrv';
|
||||||
import { NavModelSrv } from './nav_model_srv';
|
import { NavModelSrv } from './nav_model_srv';
|
||||||
import { geminiScrollbar } from './components/scroll/scroll';
|
import { geminiScrollbar } from './components/scroll/scroll';
|
||||||
import { orgSwitcher } from './components/org_switcher';
|
|
||||||
import { profiler } from './profiler';
|
import { profiler } from './profiler';
|
||||||
import { registerAngularDirectives } from './angular_wrappers';
|
import { registerAngularDirectives } from './angular_wrappers';
|
||||||
import { updateLegendValues } from './time_series2';
|
import { updateLegendValues } from './time_series2';
|
||||||
@ -72,7 +71,6 @@ export {
|
|||||||
NavModelSrv,
|
NavModelSrv,
|
||||||
NavModel,
|
NavModel,
|
||||||
geminiScrollbar,
|
geminiScrollbar,
|
||||||
orgSwitcher,
|
|
||||||
manageDashboardsDirective,
|
manageDashboardsDirective,
|
||||||
TimeSeries,
|
TimeSeries,
|
||||||
updateLegendValues,
|
updateLegendValues,
|
||||||
|
51
public/app/core/specs/OrgSwitcher.test.tsx
Normal file
51
public/app/core/specs/OrgSwitcher.test.tsx
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import React from 'react';
|
||||||
|
// @ts-ignore
|
||||||
|
import { getBackendSrv } from '@grafana/runtime/src/services/backendSrv';
|
||||||
|
import { OrgSwitcher } from '../components/OrgSwitcher';
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
import { OrgRole } from '@grafana/data';
|
||||||
|
|
||||||
|
const getMock = jest.fn(() => Promise.resolve([]));
|
||||||
|
const postMock = jest.fn();
|
||||||
|
|
||||||
|
jest.mock('@grafana/runtime/src/services/backendSrv', () => ({
|
||||||
|
getBackendSrv: () => ({
|
||||||
|
get: getMock,
|
||||||
|
post: postMock,
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('app/core/services/context_srv', () => ({
|
||||||
|
contextSrv: {
|
||||||
|
user: { orgId: 1 },
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('app/core/config', () => {
|
||||||
|
return {
|
||||||
|
appSubUrl: '/subUrl',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
let wrapper;
|
||||||
|
let orgSwitcher: OrgSwitcher;
|
||||||
|
|
||||||
|
describe('OrgSwitcher', () => {
|
||||||
|
describe('when switching org', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
wrapper = shallow(<OrgSwitcher onDismiss={() => {}} isOpen={true} />);
|
||||||
|
orgSwitcher = wrapper.instance() as OrgSwitcher;
|
||||||
|
orgSwitcher.setWindowLocation = jest.fn();
|
||||||
|
wrapper.update();
|
||||||
|
await orgSwitcher.setCurrentOrg({ name: 'mock org', orgId: 2, role: OrgRole.Viewer });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should switch orgId in call to backend', () => {
|
||||||
|
expect(postMock).toBeCalledWith('/api/user/using/2');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should switch orgId in url and redirect to home page', () => {
|
||||||
|
expect(orgSwitcher.setWindowLocation).toBeCalledWith('/subUrl/?orgId=2');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -1,48 +0,0 @@
|
|||||||
import { OrgSwitchCtrl } from '../components/org_switcher';
|
|
||||||
// @ts-ignore
|
|
||||||
import q from 'q';
|
|
||||||
|
|
||||||
jest.mock('app/core/services/context_srv', () => ({
|
|
||||||
contextSrv: {
|
|
||||||
user: { orgId: 1 },
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('app/core/config', () => {
|
|
||||||
return {
|
|
||||||
appSubUrl: '/subUrl',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('OrgSwitcher', () => {
|
|
||||||
describe('when switching org', () => {
|
|
||||||
let expectedHref: string;
|
|
||||||
let expectedUsingUrl: string;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
const backendSrvStub: any = {
|
|
||||||
get: (url: string) => {
|
|
||||||
return q.resolve([]);
|
|
||||||
},
|
|
||||||
post: (url: string) => {
|
|
||||||
expectedUsingUrl = url;
|
|
||||||
return q.resolve({});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const orgSwitcherCtrl = new OrgSwitchCtrl(backendSrvStub);
|
|
||||||
|
|
||||||
orgSwitcherCtrl.setWindowLocation = href => (expectedHref = href);
|
|
||||||
|
|
||||||
return orgSwitcherCtrl.setUsingOrg({ orgId: 2 });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should switch orgId in call to backend', () => {
|
|
||||||
expect(expectedUsingUrl).toBe('/api/user/using/2');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should switch orgId in url and redirect to home page', () => {
|
|
||||||
expect(expectedHref).toBe('/subUrl/?orgId=2');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -39,16 +39,7 @@ export class PanelInspector extends PureComponent<Props, State> {
|
|||||||
// TODO? should we get the result with an observable once?
|
// TODO? should we get the result with an observable once?
|
||||||
const data = (panel.getQueryRunner() as any).lastResult;
|
const data = (panel.getQueryRunner() as any).lastResult;
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal title={panel.title} icon="fa fa-info-circle" onDismiss={this.onDismiss} isOpen={true}>
|
||||||
title={
|
|
||||||
<div className="modal-header-title">
|
|
||||||
<i className="fa fa-info-circle" />
|
|
||||||
<span className="p-l-1">{panel.title ? panel.title : 'Panel'}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
onDismiss={this.onDismiss}
|
|
||||||
isOpen={true}
|
|
||||||
>
|
|
||||||
<div className={bodyStyle}>
|
<div className={bodyStyle}>
|
||||||
<JSONFormatter json={data} open={2} />
|
<JSONFormatter json={data} open={2} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -46,8 +46,8 @@
|
|||||||
.modal-header-title {
|
.modal-header-title {
|
||||||
font-size: $font-size-h3;
|
font-size: $font-size-h3;
|
||||||
float: left;
|
float: left;
|
||||||
padding-top: $spacer * 0.75;
|
padding-top: $space-sm;
|
||||||
margin: 0 $spacer * 3 0 $spacer * 1.5;
|
margin: 0 $space-md;
|
||||||
|
|
||||||
.gicon {
|
.gicon {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
Loading…
Reference in New Issue
Block a user