mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
TopNav: KioskMode rewrite move to AppChrome responsibility and make it a global feature (#55149)
* Initial progress * Moving keybindingSrv to context * Simplfy KioskMode * Removed unused logic * Make kiosk=tv behave as before but when topnav is enabled * Minor fix * Fixing tests * Fixing bug with notice when entering kiosk mode * Fixed test
This commit is contained in:
parent
7352c181c2
commit
b8e72d6173
@ -9352,6 +9352,9 @@ exports[`better eslint`] = {
|
|||||||
[0, 0, 0, "Do not use any type assertions.", "4"],
|
[0, 0, 0, "Do not use any type assertions.", "4"],
|
||||||
[0, 0, 0, "Do not use any type assertions.", "5"]
|
[0, 0, 0, "Do not use any type assertions.", "5"]
|
||||||
],
|
],
|
||||||
|
"public/test/mocks/getGrafanaContextMock.ts:5381": [
|
||||||
|
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||||
|
],
|
||||||
"public/test/mocks/workers.ts:5381": [
|
"public/test/mocks/workers.ts:5381": [
|
||||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||||
[0, 0, 0, "Do not use any type assertions.", "1"],
|
[0, 0, 0, "Do not use any type assertions.", "1"],
|
||||||
|
@ -59,6 +59,7 @@ import { GAEchoBackend } from './core/services/echo/backends/analytics/GABackend
|
|||||||
import { RudderstackBackend } from './core/services/echo/backends/analytics/RudderstackBackend';
|
import { RudderstackBackend } from './core/services/echo/backends/analytics/RudderstackBackend';
|
||||||
import { GrafanaJavascriptAgentBackend } from './core/services/echo/backends/grafana-javascript-agent/GrafanaJavascriptAgentBackend';
|
import { GrafanaJavascriptAgentBackend } from './core/services/echo/backends/grafana-javascript-agent/GrafanaJavascriptAgentBackend';
|
||||||
import { SentryEchoBackend } from './core/services/echo/backends/sentry/SentryBackend';
|
import { SentryEchoBackend } from './core/services/echo/backends/sentry/SentryBackend';
|
||||||
|
import { KeybindingSrv } from './core/services/keybindingSrv';
|
||||||
import { initDevFeatures } from './dev';
|
import { initDevFeatures } from './dev';
|
||||||
import { getTimeSrv } from './features/dashboard/services/TimeSrv';
|
import { getTimeSrv } from './features/dashboard/services/TimeSrv';
|
||||||
import { PanelDataErrorView } from './features/panel/components/PanelDataErrorView';
|
import { PanelDataErrorView } from './features/panel/components/PanelDataErrorView';
|
||||||
@ -157,10 +158,19 @@ export class GrafanaApp {
|
|||||||
// Preload selected app plugins
|
// Preload selected app plugins
|
||||||
await preloadPlugins(config.pluginsToPreload);
|
await preloadPlugins(config.pluginsToPreload);
|
||||||
|
|
||||||
|
// initialize chrome service
|
||||||
|
const queryParams = locationService.getSearchObject();
|
||||||
|
const chromeService = new AppChromeService();
|
||||||
|
const keybindingsService = new KeybindingSrv(locationService, chromeService);
|
||||||
|
|
||||||
|
// Read initial kiosk mode from url at app startup
|
||||||
|
chromeService.setKioskModeFromUrl(queryParams.kiosk);
|
||||||
|
|
||||||
this.context = {
|
this.context = {
|
||||||
backend: backendSrv,
|
backend: backendSrv,
|
||||||
location: locationService,
|
location: locationService,
|
||||||
chrome: new AppChromeService(),
|
chrome: chromeService,
|
||||||
|
keybindings: keybindingsService,
|
||||||
config,
|
config,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import { GrafanaTheme2 } from '@grafana/data';
|
|||||||
import { config } from '@grafana/runtime';
|
import { config } from '@grafana/runtime';
|
||||||
import { useStyles2 } from '@grafana/ui';
|
import { useStyles2 } from '@grafana/ui';
|
||||||
import { useGrafana } from 'app/core/context/GrafanaContext';
|
import { useGrafana } from 'app/core/context/GrafanaContext';
|
||||||
|
import { KioskMode } from 'app/types';
|
||||||
|
|
||||||
import { MegaMenu } from '../MegaMenu/MegaMenu';
|
import { MegaMenu } from '../MegaMenu/MegaMenu';
|
||||||
|
|
||||||
@ -23,9 +24,11 @@ export function AppChrome({ children }: Props) {
|
|||||||
return <main className="main-view">{children}</main>;
|
return <main className="main-view">{children}</main>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const searchBarHidden = state.searchBarHidden || state.kioskMode === KioskMode.TV;
|
||||||
|
|
||||||
const contentClass = cx({
|
const contentClass = cx({
|
||||||
[styles.content]: true,
|
[styles.content]: true,
|
||||||
[styles.contentNoSearchBar]: state.searchBarHidden,
|
[styles.contentNoSearchBar]: searchBarHidden,
|
||||||
[styles.contentChromeless]: state.chromeless,
|
[styles.contentChromeless]: state.chromeless,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -33,21 +36,20 @@ export function AppChrome({ children }: Props) {
|
|||||||
<main className="main-view">
|
<main className="main-view">
|
||||||
{!state.chromeless && (
|
{!state.chromeless && (
|
||||||
<div className={cx(styles.topNav)}>
|
<div className={cx(styles.topNav)}>
|
||||||
{!state.searchBarHidden && <TopSearchBar />}
|
{!searchBarHidden && <TopSearchBar />}
|
||||||
<NavToolbar
|
<NavToolbar
|
||||||
searchBarHidden={state.searchBarHidden}
|
searchBarHidden={searchBarHidden}
|
||||||
sectionNav={state.sectionNav}
|
sectionNav={state.sectionNav}
|
||||||
pageNav={state.pageNav}
|
pageNav={state.pageNav}
|
||||||
actions={state.actions}
|
actions={state.actions}
|
||||||
onToggleSearchBar={chrome.toggleSearchBar}
|
onToggleSearchBar={chrome.onToggleSearchBar}
|
||||||
onToggleMegaMenu={chrome.toggleMegaMenu}
|
onToggleMegaMenu={chrome.onToggleMegaMenu}
|
||||||
|
onToggleKioskMode={chrome.onToggleKioskMode}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className={contentClass}>{children}</div>
|
<div className={contentClass}>{children}</div>
|
||||||
{!state.chromeless && (
|
{!state.chromeless && <MegaMenu searchBarHidden={searchBarHidden} onClose={() => chrome.setMegaMenu(false)} />}
|
||||||
<MegaMenu searchBarHidden={state.searchBarHidden} onClose={() => chrome.setMegaMenu(false)} />
|
|
||||||
)}
|
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
|
import { t } from '@lingui/macro';
|
||||||
import { useObservable } from 'react-use';
|
import { useObservable } from 'react-use';
|
||||||
import { BehaviorSubject } from 'rxjs';
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
|
||||||
import { NavModelItem } from '@grafana/data';
|
import { AppEvents, NavModelItem, UrlQueryValue } from '@grafana/data';
|
||||||
|
import { locationService } from '@grafana/runtime';
|
||||||
|
import appEvents from 'app/core/app_events';
|
||||||
import store from 'app/core/store';
|
import store from 'app/core/store';
|
||||||
import { isShallowEqual } from 'app/core/utils/isShallowEqual';
|
import { isShallowEqual } from 'app/core/utils/isShallowEqual';
|
||||||
|
import { KioskMode } from 'app/types';
|
||||||
|
|
||||||
import { RouteDescriptor } from '../../navigation/types';
|
import { RouteDescriptor } from '../../navigation/types';
|
||||||
|
|
||||||
@ -14,6 +18,7 @@ export interface AppChromeState {
|
|||||||
actions?: React.ReactNode;
|
actions?: React.ReactNode;
|
||||||
searchBarHidden?: boolean;
|
searchBarHidden?: boolean;
|
||||||
megaMenuOpen?: boolean;
|
megaMenuOpen?: boolean;
|
||||||
|
kioskMode: KioskMode | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultSection: NavModelItem = { text: 'Grafana' };
|
const defaultSection: NavModelItem = { text: 'Grafana' };
|
||||||
@ -27,9 +32,10 @@ export class AppChromeService {
|
|||||||
chromeless: true, // start out hidden to not flash it on pages without chrome
|
chromeless: true, // start out hidden to not flash it on pages without chrome
|
||||||
sectionNav: defaultSection,
|
sectionNav: defaultSection,
|
||||||
searchBarHidden: store.getBool(this.searchBarStorageKey, false),
|
searchBarHidden: store.getBool(this.searchBarStorageKey, false),
|
||||||
|
kioskMode: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
registerRouteRender(route: RouteDescriptor) {
|
setMatchedRoute(route: RouteDescriptor) {
|
||||||
if (this.currentRoute !== route) {
|
if (this.currentRoute !== route) {
|
||||||
this.currentRoute = route;
|
this.currentRoute = route;
|
||||||
this.routeChangeHandled = false;
|
this.routeChangeHandled = false;
|
||||||
@ -51,6 +57,9 @@ export class AppChromeService {
|
|||||||
this.routeChangeHandled = true;
|
this.routeChangeHandled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KioskMode overrides chromeless state
|
||||||
|
newState.chromeless = newState.kioskMode === KioskMode.Full || this.currentRoute?.chromeless;
|
||||||
|
|
||||||
Object.assign(newState, update);
|
Object.assign(newState, update);
|
||||||
|
|
||||||
if (!isShallowEqual(current, newState)) {
|
if (!isShallowEqual(current, newState)) {
|
||||||
@ -58,7 +67,12 @@ export class AppChromeService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleMegaMenu = () => {
|
useState() {
|
||||||
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||||
|
return useObservable(this.state, this.state.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
onToggleMegaMenu = () => {
|
||||||
this.update({ megaMenuOpen: !this.state.getValue().megaMenuOpen });
|
this.update({ megaMenuOpen: !this.state.getValue().megaMenuOpen });
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -66,14 +80,59 @@ export class AppChromeService {
|
|||||||
this.update({ megaMenuOpen });
|
this.update({ megaMenuOpen });
|
||||||
};
|
};
|
||||||
|
|
||||||
toggleSearchBar = () => {
|
onToggleSearchBar = () => {
|
||||||
const searchBarHidden = !this.state.getValue().searchBarHidden;
|
const searchBarHidden = !this.state.getValue().searchBarHidden;
|
||||||
store.set(this.searchBarStorageKey, searchBarHidden);
|
store.set(this.searchBarStorageKey, searchBarHidden);
|
||||||
this.update({ searchBarHidden });
|
this.update({ searchBarHidden });
|
||||||
};
|
};
|
||||||
|
|
||||||
useState() {
|
onToggleKioskMode = () => {
|
||||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
const nextMode = this.getNextKioskMode();
|
||||||
return useObservable(this.state, this.state.getValue());
|
this.update({ kioskMode: nextMode });
|
||||||
|
locationService.partial({ kiosk: this.getKioskUrlValue(nextMode) });
|
||||||
|
};
|
||||||
|
|
||||||
|
exitKioskMode() {
|
||||||
|
this.update({ kioskMode: undefined });
|
||||||
|
locationService.partial({ kiosk: null });
|
||||||
|
}
|
||||||
|
|
||||||
|
setKioskModeFromUrl(kiosk: UrlQueryValue) {
|
||||||
|
switch (kiosk) {
|
||||||
|
case 'tv':
|
||||||
|
this.update({ kioskMode: KioskMode.TV });
|
||||||
|
break;
|
||||||
|
case '1':
|
||||||
|
case true:
|
||||||
|
this.update({ kioskMode: KioskMode.Full });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getKioskUrlValue(mode: KioskMode | null) {
|
||||||
|
switch (mode) {
|
||||||
|
case KioskMode.TV:
|
||||||
|
return 'tv';
|
||||||
|
case KioskMode.Full:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getNextKioskMode() {
|
||||||
|
const { kioskMode, searchBarHidden } = this.state.getValue();
|
||||||
|
|
||||||
|
if (searchBarHidden || kioskMode === KioskMode.TV) {
|
||||||
|
appEvents.emit(AppEvents.alertSuccess, [
|
||||||
|
t({ id: 'navigation.kiosk.tv-alert', message: 'Press ESC to exit kiosk mode' }),
|
||||||
|
]);
|
||||||
|
return KioskMode.Full;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!kioskMode) {
|
||||||
|
return KioskMode.TV;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ import { TOP_BAR_LEVEL_HEIGHT } from './types';
|
|||||||
export interface Props {
|
export interface Props {
|
||||||
onToggleSearchBar(): void;
|
onToggleSearchBar(): void;
|
||||||
onToggleMegaMenu(): void;
|
onToggleMegaMenu(): void;
|
||||||
|
onToggleKioskMode(): void;
|
||||||
searchBarHidden?: boolean;
|
searchBarHidden?: boolean;
|
||||||
sectionNav: NavModelItem;
|
sectionNav: NavModelItem;
|
||||||
pageNav?: NavModelItem;
|
pageNav?: NavModelItem;
|
||||||
@ -26,6 +27,7 @@ export function NavToolbar({
|
|||||||
pageNav,
|
pageNav,
|
||||||
onToggleMegaMenu,
|
onToggleMegaMenu,
|
||||||
onToggleSearchBar,
|
onToggleSearchBar,
|
||||||
|
onToggleKioskMode,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
const breadcrumbs = buildBreadcrumbs(sectionNav, pageNav);
|
const breadcrumbs = buildBreadcrumbs(sectionNav, pageNav);
|
||||||
@ -39,7 +41,10 @@ export function NavToolbar({
|
|||||||
<div className={styles.actions}>
|
<div className={styles.actions}>
|
||||||
{actions}
|
{actions}
|
||||||
{actions && <NavToolbarSeparator />}
|
{actions && <NavToolbarSeparator />}
|
||||||
<ToolbarButton onClick={onToggleSearchBar} narrow tooltip="Toggle top search bar">
|
{searchBarHidden && (
|
||||||
|
<ToolbarButton onClick={onToggleKioskMode} narrow title="Enable kiosk mode" icon="monitor" />
|
||||||
|
)}
|
||||||
|
<ToolbarButton onClick={onToggleSearchBar} narrow title="Toggle top search bar">
|
||||||
<Icon name={searchBarHidden ? 'angle-down' : 'angle-up'} size="xl" />
|
<Icon name={searchBarHidden ? 'angle-down' : 'angle-up'} size="xl" />
|
||||||
</ToolbarButton>
|
</ToolbarButton>
|
||||||
</div>
|
</div>
|
||||||
|
@ -36,7 +36,7 @@ const setup = () => {
|
|||||||
const context = getGrafanaContextMock();
|
const context = getGrafanaContextMock();
|
||||||
const store = configureStore({ navBarTree });
|
const store = configureStore({ navBarTree });
|
||||||
|
|
||||||
context.chrome.toggleMegaMenu();
|
context.chrome.onToggleMegaMenu();
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
|
@ -10,7 +10,7 @@ import { GrafanaTheme2, NavModelItem, NavSection } from '@grafana/data';
|
|||||||
import { config, locationSearchToObject, locationService, reportInteraction } from '@grafana/runtime';
|
import { config, locationSearchToObject, locationService, reportInteraction } from '@grafana/runtime';
|
||||||
import { Icon, useTheme2, CustomScrollbar } from '@grafana/ui';
|
import { Icon, useTheme2, CustomScrollbar } from '@grafana/ui';
|
||||||
import { getKioskMode } from 'app/core/navigation/kiosk';
|
import { getKioskMode } from 'app/core/navigation/kiosk';
|
||||||
import { KioskMode, StoreState } from 'app/types';
|
import { StoreState } from 'app/types';
|
||||||
|
|
||||||
import { OrgSwitcher } from '../OrgSwitcher';
|
import { OrgSwitcher } from '../OrgSwitcher';
|
||||||
|
|
||||||
@ -177,9 +177,8 @@ export const NavBar = React.memo(() => {
|
|||||||
|
|
||||||
function shouldHideNavBar(location: HistoryLocation) {
|
function shouldHideNavBar(location: HistoryLocation) {
|
||||||
const queryParams = locationSearchToObject(location.search);
|
const queryParams = locationSearchToObject(location.search);
|
||||||
const kiosk = getKioskMode(queryParams);
|
|
||||||
|
|
||||||
if (kiosk !== KioskMode.Off) {
|
if (getKioskMode(queryParams)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,12 +5,14 @@ import { LocationService } from '@grafana/runtime/src/services/LocationService';
|
|||||||
import { BackendSrv } from '@grafana/runtime/src/services/backendSrv';
|
import { BackendSrv } from '@grafana/runtime/src/services/backendSrv';
|
||||||
|
|
||||||
import { AppChromeService } from '../components/AppChrome/AppChromeService';
|
import { AppChromeService } from '../components/AppChrome/AppChromeService';
|
||||||
|
import { KeybindingSrv } from '../services/keybindingSrv';
|
||||||
|
|
||||||
export interface GrafanaContextType {
|
export interface GrafanaContextType {
|
||||||
backend: BackendSrv;
|
backend: BackendSrv;
|
||||||
location: LocationService;
|
location: LocationService;
|
||||||
config: GrafanaConfig;
|
config: GrafanaConfig;
|
||||||
chrome: AppChromeService;
|
chrome: AppChromeService;
|
||||||
|
keybindings: KeybindingSrv;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const GrafanaContext = React.createContext<GrafanaContextType | undefined>(undefined);
|
export const GrafanaContext = React.createContext<GrafanaContextType | undefined>(undefined);
|
||||||
|
@ -3,18 +3,7 @@ import { colors, JsonExplorer } from '@grafana/ui/';
|
|||||||
import appEvents from './app_events';
|
import appEvents from './app_events';
|
||||||
import { profiler } from './profiler';
|
import { profiler } from './profiler';
|
||||||
import { contextSrv } from './services/context_srv';
|
import { contextSrv } from './services/context_srv';
|
||||||
import { KeybindingSrv } from './services/keybindingSrv';
|
|
||||||
import TimeSeries, { updateLegendValues } from './time_series2';
|
import TimeSeries, { updateLegendValues } from './time_series2';
|
||||||
import { assignModelProperties } from './utils/model_utils';
|
import { assignModelProperties } from './utils/model_utils';
|
||||||
|
|
||||||
export {
|
export { profiler, appEvents, colors, assignModelProperties, contextSrv, JsonExplorer, TimeSeries, updateLegendValues };
|
||||||
profiler,
|
|
||||||
appEvents,
|
|
||||||
colors,
|
|
||||||
assignModelProperties,
|
|
||||||
contextSrv,
|
|
||||||
KeybindingSrv,
|
|
||||||
JsonExplorer,
|
|
||||||
TimeSeries,
|
|
||||||
updateLegendValues,
|
|
||||||
};
|
|
||||||
|
@ -5,21 +5,21 @@ import Drop from 'tether-drop';
|
|||||||
import { locationSearchToObject, navigationLogger, reportPageview } from '@grafana/runtime';
|
import { locationSearchToObject, navigationLogger, reportPageview } from '@grafana/runtime';
|
||||||
|
|
||||||
import { useGrafana } from '../context/GrafanaContext';
|
import { useGrafana } from '../context/GrafanaContext';
|
||||||
import { keybindingSrv } from '../services/keybindingSrv';
|
|
||||||
|
|
||||||
import { GrafanaRouteComponentProps, RouteDescriptor } from './types';
|
import { GrafanaRouteComponentProps, RouteDescriptor } from './types';
|
||||||
|
|
||||||
export interface Props extends Omit<GrafanaRouteComponentProps, 'queryParams'> {}
|
export interface Props extends Omit<GrafanaRouteComponentProps, 'queryParams'> {}
|
||||||
|
|
||||||
export function GrafanaRoute(props: Props) {
|
export function GrafanaRoute(props: Props) {
|
||||||
const { chrome } = useGrafana();
|
const { chrome, keybindings } = useGrafana();
|
||||||
|
|
||||||
chrome.registerRouteRender(props.route);
|
chrome.setMatchedRoute(props.route);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
keybindings.clearAndInitGlobalBindings();
|
||||||
|
|
||||||
updateBodyClassNames(props.route);
|
updateBodyClassNames(props.route);
|
||||||
cleanupDOM();
|
cleanupDOM();
|
||||||
reportPageview();
|
|
||||||
navigationLogger('GrafanaRoute', false, 'Mounted', props.match);
|
navigationLogger('GrafanaRoute', false, 'Mounted', props.match);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
@ -30,12 +30,6 @@ export function GrafanaRoute(props: Props) {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// unbinds all and re-bind global keybindins
|
|
||||||
keybindingSrv.reset();
|
|
||||||
keybindingSrv.initGlobals();
|
|
||||||
}, [chrome, props.route]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
cleanupDOM();
|
cleanupDOM();
|
||||||
reportPageview();
|
reportPageview();
|
||||||
|
@ -1,33 +1,9 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { UrlQueryMap } from '@grafana/data';
|
||||||
|
|
||||||
import { AppEvents, UrlQueryMap } from '@grafana/data';
|
|
||||||
import { locationService } from '@grafana/runtime';
|
|
||||||
|
|
||||||
import { KioskMode } from '../../types';
|
import { KioskMode } from '../../types';
|
||||||
import appEvents from '../app_events';
|
|
||||||
|
|
||||||
export function toggleKioskMode() {
|
// TODO Remove after topnav feature toggle is permanent and old NavBar is removed
|
||||||
let kiosk = locationService.getSearchObject().kiosk;
|
export function getKioskMode(queryParams: UrlQueryMap): KioskMode | null {
|
||||||
|
|
||||||
switch (kiosk) {
|
|
||||||
case 'tv':
|
|
||||||
kiosk = true;
|
|
||||||
appEvents.emit(AppEvents.alertSuccess, [
|
|
||||||
t({ id: 'navigation.kiosk.tv-alert', message: 'Press ESC to exit Kiosk mode' }),
|
|
||||||
]);
|
|
||||||
break;
|
|
||||||
case '1':
|
|
||||||
case true:
|
|
||||||
kiosk = null;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
kiosk = 'tv';
|
|
||||||
}
|
|
||||||
|
|
||||||
locationService.partial({ kiosk });
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getKioskMode(queryParams: UrlQueryMap): KioskMode {
|
|
||||||
switch (queryParams.kiosk) {
|
switch (queryParams.kiosk) {
|
||||||
case 'tv':
|
case 'tv':
|
||||||
return KioskMode.TV;
|
return KioskMode.TV;
|
||||||
@ -36,10 +12,6 @@ export function getKioskMode(queryParams: UrlQueryMap): KioskMode {
|
|||||||
case true:
|
case true:
|
||||||
return KioskMode.Full;
|
return KioskMode.Full;
|
||||||
default:
|
default:
|
||||||
return KioskMode.Off;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function exitKioskMode() {
|
|
||||||
locationService.partial({ kiosk: null });
|
|
||||||
}
|
|
||||||
|
@ -3,7 +3,7 @@ import Mousetrap from 'mousetrap';
|
|||||||
import 'mousetrap-global-bind';
|
import 'mousetrap-global-bind';
|
||||||
import 'mousetrap/plugins/global-bind/mousetrap-global-bind';
|
import 'mousetrap/plugins/global-bind/mousetrap-global-bind';
|
||||||
import { LegacyGraphHoverClearEvent, locationUtil } from '@grafana/data';
|
import { LegacyGraphHoverClearEvent, locationUtil } from '@grafana/data';
|
||||||
import { config, locationService } from '@grafana/runtime';
|
import { config, LocationService } from '@grafana/runtime';
|
||||||
import appEvents from 'app/core/app_events';
|
import appEvents from 'app/core/app_events';
|
||||||
import { getExploreUrl } from 'app/core/utils/explore';
|
import { getExploreUrl } from 'app/core/utils/explore';
|
||||||
import { SaveDashboardDrawer } from 'app/features/dashboard/components/SaveDashboard/SaveDashboardDrawer';
|
import { SaveDashboardDrawer } from 'app/features/dashboard/components/SaveDashboard/SaveDashboardDrawer';
|
||||||
@ -20,20 +20,20 @@ import {
|
|||||||
ZoomOutEvent,
|
ZoomOutEvent,
|
||||||
AbsoluteTimeEvent,
|
AbsoluteTimeEvent,
|
||||||
} from '../../types/events';
|
} from '../../types/events';
|
||||||
|
import { AppChromeService } from '../components/AppChrome/AppChromeService';
|
||||||
import { HelpModal } from '../components/help/HelpModal';
|
import { HelpModal } from '../components/help/HelpModal';
|
||||||
import { contextSrv } from '../core';
|
import { contextSrv } from '../core';
|
||||||
import { exitKioskMode, toggleKioskMode } from '../navigation/kiosk';
|
|
||||||
|
|
||||||
import { toggleTheme } from './toggleTheme';
|
import { toggleTheme } from './toggleTheme';
|
||||||
import { withFocusedPanel } from './withFocusedPanelId';
|
import { withFocusedPanel } from './withFocusedPanelId';
|
||||||
|
|
||||||
export class KeybindingSrv {
|
export class KeybindingSrv {
|
||||||
reset() {
|
constructor(private locationService: LocationService, private chromeService: AppChromeService) {}
|
||||||
Mousetrap.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
initGlobals() {
|
clearAndInitGlobalBindings() {
|
||||||
if (locationService.getLocation().pathname !== '/login') {
|
Mousetrap.reset();
|
||||||
|
|
||||||
|
if (this.locationService.getLocation().pathname !== '/login') {
|
||||||
this.bind(['?', 'h'], this.showHelpModal);
|
this.bind(['?', 'h'], this.showHelpModal);
|
||||||
this.bind('g h', this.goToHome);
|
this.bind('g h', this.goToHome);
|
||||||
this.bind('g a', this.openAlerting);
|
this.bind('g a', this.openAlerting);
|
||||||
@ -42,7 +42,7 @@ export class KeybindingSrv {
|
|||||||
this.bind('t a', this.makeAbsoluteTime);
|
this.bind('t a', this.makeAbsoluteTime);
|
||||||
this.bind('f', this.openSearch);
|
this.bind('f', this.openSearch);
|
||||||
this.bind('esc', this.exit);
|
this.bind('esc', this.exit);
|
||||||
this.bindGlobal('esc', this.globalEsc);
|
this.bindGlobalEsc();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.bind('t t', () => toggleTheme(false));
|
this.bind('t t', () => toggleTheme(false));
|
||||||
@ -53,6 +53,10 @@ export class KeybindingSrv {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bindGlobalEsc() {
|
||||||
|
this.bindGlobal('esc', this.globalEsc);
|
||||||
|
}
|
||||||
|
|
||||||
globalEsc() {
|
globalEsc() {
|
||||||
const anyDoc = document as any;
|
const anyDoc = document as any;
|
||||||
const activeElement = anyDoc.activeElement;
|
const activeElement = anyDoc.activeElement;
|
||||||
@ -82,29 +86,29 @@ export class KeybindingSrv {
|
|||||||
toggleNav() {
|
toggleNav() {
|
||||||
window.location.href =
|
window.location.href =
|
||||||
config.appSubUrl +
|
config.appSubUrl +
|
||||||
locationUtil.getUrlForPartial(locationService.getLocation(), {
|
locationUtil.getUrlForPartial(this.locationService.getLocation(), {
|
||||||
'__feature.topnav': (!config.featureToggles.topnav).toString(),
|
'__feature.topnav': (!config.featureToggles.topnav).toString(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private openSearch() {
|
private openSearch() {
|
||||||
locationService.partial({ search: 'open' });
|
this.locationService.partial({ search: 'open' });
|
||||||
}
|
}
|
||||||
|
|
||||||
private closeSearch() {
|
private closeSearch() {
|
||||||
locationService.partial({ search: null });
|
this.locationService.partial({ search: null });
|
||||||
}
|
}
|
||||||
|
|
||||||
private openAlerting() {
|
private openAlerting() {
|
||||||
locationService.push('/alerting');
|
this.locationService.push('/alerting');
|
||||||
}
|
}
|
||||||
|
|
||||||
private goToHome() {
|
private goToHome() {
|
||||||
locationService.push('/');
|
this.locationService.push('/');
|
||||||
}
|
}
|
||||||
|
|
||||||
private goToProfile() {
|
private goToProfile() {
|
||||||
locationService.push('/profile');
|
this.locationService.push('/profile');
|
||||||
}
|
}
|
||||||
|
|
||||||
private makeAbsoluteTime() {
|
private makeAbsoluteTime() {
|
||||||
@ -116,30 +120,31 @@ export class KeybindingSrv {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private exit() {
|
private exit() {
|
||||||
const search = locationService.getSearchObject();
|
const search = this.locationService.getSearchObject();
|
||||||
|
|
||||||
if (search.editview) {
|
if (search.editview) {
|
||||||
locationService.partial({ editview: null, editIndex: null });
|
this.locationService.partial({ editview: null, editIndex: null });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (search.inspect) {
|
if (search.inspect) {
|
||||||
locationService.partial({ inspect: null, inspectTab: null });
|
this.locationService.partial({ inspect: null, inspectTab: null });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (search.editPanel) {
|
if (search.editPanel) {
|
||||||
locationService.partial({ editPanel: null, tab: null });
|
this.locationService.partial({ editPanel: null, tab: null });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (search.viewPanel) {
|
if (search.viewPanel) {
|
||||||
locationService.partial({ viewPanel: null, tab: null });
|
this.locationService.partial({ viewPanel: null, tab: null });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (search.kiosk) {
|
const { kioskMode } = this.chromeService.state.getValue();
|
||||||
exitKioskMode();
|
if (kioskMode) {
|
||||||
|
this.chromeService.exitKioskMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (search.search) {
|
if (search.search) {
|
||||||
@ -148,7 +153,7 @@ export class KeybindingSrv {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private showDashEditView() {
|
private showDashEditView() {
|
||||||
locationService.partial({
|
this.locationService.partial({
|
||||||
editview: 'settings',
|
editview: 'settings',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -230,15 +235,15 @@ export class KeybindingSrv {
|
|||||||
// edit panel
|
// edit panel
|
||||||
this.bindWithPanelId('e', (panelId) => {
|
this.bindWithPanelId('e', (panelId) => {
|
||||||
if (dashboard.canEditPanelById(panelId)) {
|
if (dashboard.canEditPanelById(panelId)) {
|
||||||
const isEditing = locationService.getSearchObject().editPanel !== undefined;
|
const isEditing = this.locationService.getSearchObject().editPanel !== undefined;
|
||||||
locationService.partial({ editPanel: isEditing ? null : panelId });
|
this.locationService.partial({ editPanel: isEditing ? null : panelId });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// view panel
|
// view panel
|
||||||
this.bindWithPanelId('v', (panelId) => {
|
this.bindWithPanelId('v', (panelId) => {
|
||||||
const isViewing = locationService.getSearchObject().viewPanel !== undefined;
|
const isViewing = this.locationService.getSearchObject().viewPanel !== undefined;
|
||||||
locationService.partial({ viewPanel: isViewing ? null : panelId });
|
this.locationService.partial({ viewPanel: isViewing ? null : panelId });
|
||||||
});
|
});
|
||||||
|
|
||||||
//toggle legend
|
//toggle legend
|
||||||
@ -252,7 +257,7 @@ export class KeybindingSrv {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.bindWithPanelId('i', (panelId) => {
|
this.bindWithPanelId('i', (panelId) => {
|
||||||
locationService.partial({ inspect: panelId });
|
this.locationService.partial({ inspect: panelId });
|
||||||
});
|
});
|
||||||
|
|
||||||
// jump to explore if permissions allow
|
// jump to explore if permissions allow
|
||||||
@ -268,7 +273,7 @@ export class KeybindingSrv {
|
|||||||
if (url) {
|
if (url) {
|
||||||
const urlWithoutBase = locationUtil.stripBaseFromUrl(url);
|
const urlWithoutBase = locationUtil.stripBaseFromUrl(url);
|
||||||
if (urlWithoutBase) {
|
if (urlWithoutBase) {
|
||||||
locationService.push(urlWithoutBase);
|
this.locationService.push(urlWithoutBase);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -322,7 +327,7 @@ export class KeybindingSrv {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.bind('d n', () => {
|
this.bind('d n', () => {
|
||||||
locationService.push('/dashboard/new');
|
this.locationService.push('/dashboard/new');
|
||||||
});
|
});
|
||||||
|
|
||||||
this.bind('d r', () => {
|
this.bind('d r', () => {
|
||||||
@ -334,17 +339,15 @@ export class KeybindingSrv {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.bind('d k', () => {
|
this.bind('d k', () => {
|
||||||
toggleKioskMode();
|
this.chromeService.onToggleKioskMode();
|
||||||
});
|
});
|
||||||
|
|
||||||
//Autofit panels
|
//Autofit panels
|
||||||
this.bind('d a', () => {
|
this.bind('d a', () => {
|
||||||
// this has to be a full page reload
|
// this has to be a full page reload
|
||||||
const queryParams = locationService.getSearchObject();
|
const queryParams = this.locationService.getSearchObject();
|
||||||
const newUrlParam = queryParams.autofitpanels ? '' : '&autofitpanels';
|
const newUrlParam = queryParams.autofitpanels ? '' : '&autofitpanels';
|
||||||
window.location.href = window.location.href + newUrlParam;
|
window.location.href = window.location.href + newUrlParam;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const keybindingSrv = new KeybindingSrv();
|
|
||||||
|
@ -18,10 +18,9 @@ import { useSelector } from 'react-redux';
|
|||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { reportInteraction, locationService } from '@grafana/runtime';
|
import { reportInteraction, locationService } from '@grafana/runtime';
|
||||||
import { useStyles2 } from '@grafana/ui';
|
import { useStyles2 } from '@grafana/ui';
|
||||||
|
import { useGrafana } from 'app/core/context/GrafanaContext';
|
||||||
import { StoreState } from 'app/types';
|
import { StoreState } from 'app/types';
|
||||||
|
|
||||||
import { keybindingSrv } from '../../core/services/keybindingSrv';
|
|
||||||
|
|
||||||
import { ResultItem } from './ResultItem';
|
import { ResultItem } from './ResultItem';
|
||||||
import getDashboardNavActions from './actions/dashboard.nav.actions';
|
import getDashboardNavActions from './actions/dashboard.nav.actions';
|
||||||
import getGlobalActions from './actions/global.static.actions';
|
import getGlobalActions from './actions/global.static.actions';
|
||||||
@ -33,6 +32,7 @@ import getGlobalActions from './actions/global.static.actions';
|
|||||||
|
|
||||||
export const CommandPalette = () => {
|
export const CommandPalette = () => {
|
||||||
const styles = useStyles2(getSearchStyles);
|
const styles = useStyles2(getSearchStyles);
|
||||||
|
const { keybindings } = useGrafana();
|
||||||
const [actions, setActions] = useState<Action[]>([]);
|
const [actions, setActions] = useState<Action[]>([]);
|
||||||
const [staticActions, setStaticActions] = useState<Action[]>([]);
|
const [staticActions, setStaticActions] = useState<Action[]>([]);
|
||||||
const { query, showing } = useKBar((state) => ({
|
const { query, showing } = useKBar((state) => ({
|
||||||
@ -63,14 +63,14 @@ export const CommandPalette = () => {
|
|||||||
setActions([...staticActions, ...dashAct]);
|
setActions([...staticActions, ...dashAct]);
|
||||||
});
|
});
|
||||||
|
|
||||||
keybindingSrv.bindGlobal('esc', () => {
|
keybindings.bindGlobal('esc', () => {
|
||||||
query.setVisualState(VisualState.animatingOut);
|
query.setVisualState(VisualState.animatingOut);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
keybindingSrv.bindGlobal('esc', () => {
|
keybindings.bindGlobal('esc', () => {
|
||||||
keybindingSrv.globalEsc();
|
keybindings.globalEsc();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
@ -18,7 +18,7 @@ import {
|
|||||||
import { AppChromeUpdate } from 'app/core/components/AppChrome/AppChromeUpdate';
|
import { AppChromeUpdate } from 'app/core/components/AppChrome/AppChromeUpdate';
|
||||||
import { NavToolbarSeparator } from 'app/core/components/AppChrome/NavToolbarSeparator';
|
import { NavToolbarSeparator } from 'app/core/components/AppChrome/NavToolbarSeparator';
|
||||||
import config from 'app/core/config';
|
import config from 'app/core/config';
|
||||||
import { toggleKioskMode } from 'app/core/navigation/kiosk';
|
import { useGrafana } from 'app/core/context/GrafanaContext';
|
||||||
import { DashboardCommentsModal } from 'app/features/dashboard/components/DashboardComments/DashboardCommentsModal';
|
import { DashboardCommentsModal } from 'app/features/dashboard/components/DashboardComments/DashboardCommentsModal';
|
||||||
import { SaveDashboardDrawer } from 'app/features/dashboard/components/SaveDashboard/SaveDashboardDrawer';
|
import { SaveDashboardDrawer } from 'app/features/dashboard/components/SaveDashboard/SaveDashboardDrawer';
|
||||||
import { ShareModal } from 'app/features/dashboard/components/ShareModal';
|
import { ShareModal } from 'app/features/dashboard/components/ShareModal';
|
||||||
@ -45,7 +45,7 @@ const selectors = e2eSelectors.pages.Dashboard.DashNav;
|
|||||||
export interface OwnProps {
|
export interface OwnProps {
|
||||||
dashboard: DashboardModel;
|
dashboard: DashboardModel;
|
||||||
isFullscreen: boolean;
|
isFullscreen: boolean;
|
||||||
kioskMode: KioskMode;
|
kioskMode?: KioskMode | null;
|
||||||
hideTimePicker: boolean;
|
hideTimePicker: boolean;
|
||||||
folderTitle?: string;
|
folderTitle?: string;
|
||||||
title: string;
|
title: string;
|
||||||
@ -73,6 +73,7 @@ type Props = OwnProps & ConnectedProps<typeof connector>;
|
|||||||
|
|
||||||
export const DashNav = React.memo<Props>((props) => {
|
export const DashNav = React.memo<Props>((props) => {
|
||||||
const forceUpdate = useForceUpdate();
|
const forceUpdate = useForceUpdate();
|
||||||
|
const { chrome } = useGrafana();
|
||||||
|
|
||||||
const onStarDashboard = () => {
|
const onStarDashboard = () => {
|
||||||
const dashboardSrv = getDashboardSrv();
|
const dashboardSrv = getDashboardSrv();
|
||||||
@ -90,7 +91,7 @@ export const DashNav = React.memo<Props>((props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onToggleTVMode = () => {
|
const onToggleTVMode = () => {
|
||||||
toggleKioskMode();
|
chrome.onToggleKioskMode();
|
||||||
};
|
};
|
||||||
|
|
||||||
const onOpenSettings = () => {
|
const onOpenSettings = () => {
|
||||||
@ -127,7 +128,7 @@ export const DashNav = React.memo<Props>((props) => {
|
|||||||
const { canStar, canShare, isStarred } = dashboard.meta;
|
const { canStar, canShare, isStarred } = dashboard.meta;
|
||||||
const buttons: ReactNode[] = [];
|
const buttons: ReactNode[] = [];
|
||||||
|
|
||||||
if (kioskMode !== KioskMode.Off || isPlaylistRunning()) {
|
if (kioskMode || isPlaylistRunning()) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -235,7 +236,7 @@ export const DashNav = React.memo<Props>((props) => {
|
|||||||
const { snapshot } = dashboard;
|
const { snapshot } = dashboard;
|
||||||
const snapshotUrl = snapshot && snapshot.originalUrl;
|
const snapshotUrl = snapshot && snapshot.originalUrl;
|
||||||
const buttons: ReactNode[] = [];
|
const buttons: ReactNode[] = [];
|
||||||
const tvButton = (
|
const tvButton = config.featureToggles.topnav ? null : (
|
||||||
<ToolbarButton
|
<ToolbarButton
|
||||||
tooltip={t({ id: 'dashboard.toolbar.tv-button', message: 'Cycle view mode' })}
|
tooltip={t({ id: 'dashboard.toolbar.tv-button', message: 'Cycle view mode' })}
|
||||||
icon="monitor"
|
icon="monitor"
|
||||||
|
@ -5,11 +5,13 @@ import { Router } from 'react-router-dom';
|
|||||||
import { useEffectOnce } from 'react-use';
|
import { useEffectOnce } from 'react-use';
|
||||||
import { AutoSizerProps } from 'react-virtualized-auto-sizer';
|
import { AutoSizerProps } from 'react-virtualized-auto-sizer';
|
||||||
import { mockToolkitActionCreator } from 'test/core/redux/mocks';
|
import { mockToolkitActionCreator } from 'test/core/redux/mocks';
|
||||||
|
import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock';
|
||||||
|
|
||||||
import { createTheme } from '@grafana/data';
|
import { createTheme } from '@grafana/data';
|
||||||
import { selectors } from '@grafana/e2e-selectors';
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
import { config, locationService, setDataSourceSrv } from '@grafana/runtime';
|
import { config, locationService, setDataSourceSrv } from '@grafana/runtime';
|
||||||
import { notifyApp } from 'app/core/actions';
|
import { notifyApp } from 'app/core/actions';
|
||||||
|
import { GrafanaContext } from 'app/core/context/GrafanaContext';
|
||||||
import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps';
|
import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps';
|
||||||
import { DashboardInitPhase, DashboardMeta, DashboardRoutes } from 'app/types';
|
import { DashboardInitPhase, DashboardMeta, DashboardRoutes } from 'app/types';
|
||||||
|
|
||||||
@ -130,12 +132,16 @@ function dashboardPageScenario(description: string, scenarioFn: (ctx: ScenarioCo
|
|||||||
ctx.props = props;
|
ctx.props = props;
|
||||||
ctx.dashboard = props.dashboard;
|
ctx.dashboard = props.dashboard;
|
||||||
|
|
||||||
|
const context = getGrafanaContextMock();
|
||||||
|
|
||||||
const { container, rerender, unmount } = render(
|
const { container, rerender, unmount } = render(
|
||||||
<Provider store={store}>
|
<GrafanaContext.Provider value={context}>
|
||||||
<Router history={locationService.getHistory()}>
|
<Provider store={store}>
|
||||||
<UnthemedDashboardPage {...props} />
|
<Router history={locationService.getHistory()}>
|
||||||
</Router>
|
<UnthemedDashboardPage {...props} />
|
||||||
</Provider>
|
</Router>
|
||||||
|
</Provider>
|
||||||
|
</GrafanaContext.Provider>
|
||||||
);
|
);
|
||||||
|
|
||||||
ctx.container = container;
|
ctx.container = container;
|
||||||
@ -144,11 +150,13 @@ function dashboardPageScenario(description: string, scenarioFn: (ctx: ScenarioCo
|
|||||||
Object.assign(props, newProps);
|
Object.assign(props, newProps);
|
||||||
|
|
||||||
rerender(
|
rerender(
|
||||||
<Provider store={store}>
|
<GrafanaContext.Provider value={context}>
|
||||||
<Router history={locationService.getHistory()}>
|
<Provider store={store}>
|
||||||
<UnthemedDashboardPage {...props} />
|
<Router history={locationService.getHistory()}>
|
||||||
</Router>
|
<UnthemedDashboardPage {...props} />
|
||||||
</Provider>
|
</Router>
|
||||||
|
</Provider>
|
||||||
|
</GrafanaContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -179,6 +187,7 @@ describe('DashboardPage', () => {
|
|||||||
routeName: 'normal-dashboard',
|
routeName: 'normal-dashboard',
|
||||||
urlSlug: 'my-dash',
|
urlSlug: 'my-dash',
|
||||||
urlUid: '11',
|
urlUid: '11',
|
||||||
|
keybindingSrv: expect.anything(),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -9,6 +9,7 @@ import { Themeable2, withTheme2 } from '@grafana/ui';
|
|||||||
import { notifyApp } from 'app/core/actions';
|
import { notifyApp } from 'app/core/actions';
|
||||||
import { Page } from 'app/core/components/Page/Page';
|
import { Page } from 'app/core/components/Page/Page';
|
||||||
import { config } from 'app/core/config';
|
import { config } from 'app/core/config';
|
||||||
|
import { GrafanaContext } from 'app/core/context/GrafanaContext';
|
||||||
import { createErrorNotification } from 'app/core/copy/appNotification';
|
import { createErrorNotification } from 'app/core/copy/appNotification';
|
||||||
import { getKioskMode } from 'app/core/navigation/kiosk';
|
import { getKioskMode } from 'app/core/navigation/kiosk';
|
||||||
import { GrafanaRouteComponentProps } from 'app/core/navigation/types';
|
import { GrafanaRouteComponentProps } from 'app/core/navigation/types';
|
||||||
@ -96,6 +97,8 @@ export interface State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class UnthemedDashboardPage extends PureComponent<Props, State> {
|
export class UnthemedDashboardPage extends PureComponent<Props, State> {
|
||||||
|
static contextType = GrafanaContext;
|
||||||
|
|
||||||
private forceRouteReloadCounter = 0;
|
private forceRouteReloadCounter = 0;
|
||||||
state: State = this.getCleanState();
|
state: State = this.getCleanState();
|
||||||
|
|
||||||
@ -139,6 +142,7 @@ export class UnthemedDashboardPage extends PureComponent<Props, State> {
|
|||||||
routeName: this.props.route.routeName,
|
routeName: this.props.route.routeName,
|
||||||
fixUrl: !isPublic,
|
fixUrl: !isPublic,
|
||||||
accessToken: match.params.accessToken,
|
accessToken: match.params.accessToken,
|
||||||
|
keybindingSrv: this.context.keybindings,
|
||||||
});
|
});
|
||||||
|
|
||||||
// small delay to start live updates
|
// small delay to start live updates
|
||||||
@ -336,7 +340,7 @@ export class UnthemedDashboardPage extends PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const inspectPanel = this.getInspectPanel();
|
const inspectPanel = this.getInspectPanel();
|
||||||
const showSubMenu = !editPanel && kioskMode === KioskMode.Off && !this.props.queryParams.editview;
|
const showSubMenu = !editPanel && !kioskMode && !this.props.queryParams.editview;
|
||||||
|
|
||||||
const toolbar = kioskMode !== KioskMode.Full && !queryParams.editview && (
|
const toolbar = kioskMode !== KioskMode.Full && !queryParams.editview && (
|
||||||
<header data-testid={selectors.pages.Dashboard.DashNav.navV2}>
|
<header data-testid={selectors.pages.Dashboard.DashNav.navV2}>
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock';
|
||||||
|
|
||||||
|
import { GrafanaContext } from 'app/core/context/GrafanaContext';
|
||||||
import { DashboardMeta, DashboardRoutes } from 'app/types';
|
import { DashboardMeta, DashboardRoutes } from 'app/types';
|
||||||
|
|
||||||
import { getRouteComponentProps } from '../../../core/navigation/__mocks__/routeProps';
|
import { getRouteComponentProps } from '../../../core/navigation/__mocks__/routeProps';
|
||||||
@ -83,13 +85,22 @@ function soloPanelPageScenario(description: string, scenarioFn: (ctx: ScenarioCo
|
|||||||
Object.assign(props, propOverrides);
|
Object.assign(props, propOverrides);
|
||||||
|
|
||||||
ctx.dashboard = props.dashboard;
|
ctx.dashboard = props.dashboard;
|
||||||
let { rerender } = render(<SoloPanelPage {...props} />);
|
|
||||||
|
const context = getGrafanaContextMock();
|
||||||
|
const renderPage = (props: Props) => (
|
||||||
|
<GrafanaContext.Provider value={context}>
|
||||||
|
<SoloPanelPage {...props} />
|
||||||
|
</GrafanaContext.Provider>
|
||||||
|
);
|
||||||
|
|
||||||
|
let { rerender } = render(renderPage(props));
|
||||||
|
|
||||||
// prop updates will be submitted by rerendering the same component with different props
|
// prop updates will be submitted by rerendering the same component with different props
|
||||||
ctx.rerender = (newProps?: Partial<Props>) => {
|
ctx.rerender = (newProps?: Partial<Props>) => {
|
||||||
Object.assign(props, newProps);
|
rerender(renderPage(Object.assign(props, newProps)));
|
||||||
rerender(<SoloPanelPage {...props} />);
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
rerender: () => {
|
rerender: () => {
|
||||||
// will be replaced while mount() is called
|
// will be replaced while mount() is called
|
||||||
},
|
},
|
||||||
|
@ -2,6 +2,7 @@ import React, { Component } from 'react';
|
|||||||
import { connect, ConnectedProps } from 'react-redux';
|
import { connect, ConnectedProps } from 'react-redux';
|
||||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||||
|
|
||||||
|
import { GrafanaContext } from 'app/core/context/GrafanaContext';
|
||||||
import { GrafanaRouteComponentProps } from 'app/core/navigation/types';
|
import { GrafanaRouteComponentProps } from 'app/core/navigation/types';
|
||||||
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
|
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
|
||||||
import { StoreState } from 'app/types';
|
import { StoreState } from 'app/types';
|
||||||
@ -34,6 +35,8 @@ export interface State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class SoloPanelPage extends Component<Props, State> {
|
export class SoloPanelPage extends Component<Props, State> {
|
||||||
|
static contextType = GrafanaContext;
|
||||||
|
|
||||||
state: State = {
|
state: State = {
|
||||||
panel: null,
|
panel: null,
|
||||||
notFound: false,
|
notFound: false,
|
||||||
@ -48,6 +51,7 @@ export class SoloPanelPage extends Component<Props, State> {
|
|||||||
urlType: match.params.type,
|
urlType: match.params.type,
|
||||||
routeName: route.routeName,
|
routeName: route.routeName,
|
||||||
fixUrl: false,
|
fixUrl: false,
|
||||||
|
keybindingSrv: this.context.keybindings,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ import { Subject } from 'rxjs';
|
|||||||
import { FetchError, locationService, setEchoSrv } from '@grafana/runtime';
|
import { FetchError, locationService, setEchoSrv } from '@grafana/runtime';
|
||||||
import appEvents from 'app/core/app_events';
|
import appEvents from 'app/core/app_events';
|
||||||
import { getBackendSrv } from 'app/core/services/backend_srv';
|
import { getBackendSrv } from 'app/core/services/backend_srv';
|
||||||
import { keybindingSrv } from 'app/core/services/keybindingSrv';
|
import { KeybindingSrv } from 'app/core/services/keybindingSrv';
|
||||||
import { variableAdapters } from 'app/features/variables/adapters';
|
import { variableAdapters } from 'app/features/variables/adapters';
|
||||||
import { createConstantVariableAdapter } from 'app/features/variables/constant/adapter';
|
import { createConstantVariableAdapter } from 'app/features/variables/constant/adapter';
|
||||||
import { constantBuilder } from 'app/features/variables/shared/testing/builders';
|
import { constantBuilder } from 'app/features/variables/shared/testing/builders';
|
||||||
@ -193,6 +193,9 @@ function describeInitScenario(description: string, scenarioFn: ScenarioFn) {
|
|||||||
urlUid: DASH_UID,
|
urlUid: DASH_UID,
|
||||||
fixUrl: false,
|
fixUrl: false,
|
||||||
routeName: DashboardRoutes.Normal,
|
routeName: DashboardRoutes.Normal,
|
||||||
|
keybindingSrv: {
|
||||||
|
setupDashboardBindings: jest.fn(),
|
||||||
|
} as unknown as KeybindingSrv,
|
||||||
},
|
},
|
||||||
backendSrv: getBackendSrv(),
|
backendSrv: getBackendSrv(),
|
||||||
loaderSrv,
|
loaderSrv,
|
||||||
@ -221,8 +224,6 @@ function describeInitScenario(description: string, scenarioFn: ScenarioFn) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
keybindingSrv.setupDashboardBindings = jest.fn();
|
|
||||||
|
|
||||||
setDashboardSrv({
|
setDashboardSrv({
|
||||||
setCurrent: jest.fn(),
|
setCurrent: jest.fn(),
|
||||||
} as any);
|
} as any);
|
||||||
@ -273,7 +274,7 @@ describeInitScenario('Initializing new dashboard', (ctx) => {
|
|||||||
expect(getTimeSrv().init).toBeCalled();
|
expect(getTimeSrv().init).toBeCalled();
|
||||||
expect(getDashboardSrv().setCurrent).toBeCalled();
|
expect(getDashboardSrv().setCurrent).toBeCalled();
|
||||||
expect(getDashboardQueryRunner().run).toBeCalled();
|
expect(getDashboardQueryRunner().run).toBeCalled();
|
||||||
expect(keybindingSrv.setupDashboardBindings).toBeCalled();
|
expect(ctx.args.keybindingSrv.setupDashboardBindings).toBeCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -408,7 +409,7 @@ describeInitScenario('Initializing existing dashboard', (ctx) => {
|
|||||||
expect(getTimeSrv().init).toBeCalled();
|
expect(getTimeSrv().init).toBeCalled();
|
||||||
expect(getDashboardSrv().setCurrent).toBeCalled();
|
expect(getDashboardSrv().setCurrent).toBeCalled();
|
||||||
expect(getDashboardQueryRunner().run).toBeCalled();
|
expect(getDashboardQueryRunner().run).toBeCalled();
|
||||||
expect(keybindingSrv.setupDashboardBindings).toBeCalled();
|
expect(ctx.args.keybindingSrv.setupDashboardBindings).toBeCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should initialize redux variables if newVariables is enabled', () => {
|
it('Should initialize redux variables if newVariables is enabled', () => {
|
||||||
|
@ -4,7 +4,7 @@ import { notifyApp } from 'app/core/actions';
|
|||||||
import appEvents from 'app/core/app_events';
|
import appEvents from 'app/core/app_events';
|
||||||
import { createErrorNotification } from 'app/core/copy/appNotification';
|
import { createErrorNotification } from 'app/core/copy/appNotification';
|
||||||
import { backendSrv } from 'app/core/services/backend_srv';
|
import { backendSrv } from 'app/core/services/backend_srv';
|
||||||
import { keybindingSrv } from 'app/core/services/keybindingSrv';
|
import { KeybindingSrv } from 'app/core/services/keybindingSrv';
|
||||||
import store from 'app/core/store';
|
import store from 'app/core/store';
|
||||||
import { dashboardLoaderSrv } from 'app/features/dashboard/services/DashboardLoaderSrv';
|
import { dashboardLoaderSrv } from 'app/features/dashboard/services/DashboardLoaderSrv';
|
||||||
import { DashboardSrv, getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
|
import { DashboardSrv, getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
|
||||||
@ -32,6 +32,7 @@ export interface InitDashboardArgs {
|
|||||||
accessToken?: string;
|
accessToken?: string;
|
||||||
routeName?: string;
|
routeName?: string;
|
||||||
fixUrl: boolean;
|
fixUrl: boolean;
|
||||||
|
keybindingSrv: KeybindingSrv;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchDashboard(
|
async function fetchDashboard(
|
||||||
@ -213,7 +214,7 @@ export function initDashboard(args: InitDashboardArgs): ThunkResult<void> {
|
|||||||
dashboard.autoFitPanels(window.innerHeight, queryParams.kiosk);
|
dashboard.autoFitPanels(window.innerHeight, queryParams.kiosk);
|
||||||
}
|
}
|
||||||
|
|
||||||
keybindingSrv.setupDashboardBindings(dashboard);
|
args.keybindingSrv.setupDashboardBindings(dashboard);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof Error) {
|
if (err instanceof Error) {
|
||||||
dispatch(notifyApp(createErrorNotification('Dashboard init failed', err)));
|
dispatch(notifyApp(createErrorNotification('Dashboard init failed', err)));
|
||||||
|
@ -57,6 +57,7 @@ class WrapperUnconnected extends PureComponent<Props> {
|
|||||||
//This is needed for breadcrumbs and topnav.
|
//This is needed for breadcrumbs and topnav.
|
||||||
//We should probably abstract this out at some point
|
//We should probably abstract this out at some point
|
||||||
this.context.chrome.update({ sectionNav: this.props.navModel.node });
|
this.context.chrome.update({ sectionNav: this.props.navModel.node });
|
||||||
|
this.context.keybindings.setupTimeRangeBindings(false);
|
||||||
|
|
||||||
lastSavedUrl.left = undefined;
|
lastSavedUrl.left = undefined;
|
||||||
lastSavedUrl.right = undefined;
|
lastSavedUrl.right = undefined;
|
||||||
|
@ -14,7 +14,6 @@ import {
|
|||||||
DataSourceRef,
|
DataSourceRef,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { getDataSourceSrv } from '@grafana/runtime';
|
import { getDataSourceSrv } from '@grafana/runtime';
|
||||||
import { keybindingSrv } from 'app/core/services/keybindingSrv';
|
|
||||||
import {
|
import {
|
||||||
DEFAULT_RANGE,
|
DEFAULT_RANGE,
|
||||||
getQueryKeys,
|
getQueryKeys,
|
||||||
@ -178,8 +177,6 @@ export function initializeExplore(
|
|||||||
}
|
}
|
||||||
dispatch(updateTime({ exploreId }));
|
dispatch(updateTime({ exploreId }));
|
||||||
|
|
||||||
keybindingSrv.setupTimeRangeBindings(false);
|
|
||||||
|
|
||||||
if (instance) {
|
if (instance) {
|
||||||
// We do not want to add the url to browser history on init because when the pane is initialised it's because
|
// We do not want to add the url to browser history on init because when the pane is initialised it's because
|
||||||
// we already have something in the url. Adding basically the same state as additional history item prevents
|
// we already have something in the url. Adding basically the same state as additional history item prevents
|
||||||
|
@ -90,7 +90,6 @@ export interface DashboardInitError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum KioskMode {
|
export enum KioskMode {
|
||||||
Off = 'off',
|
|
||||||
TV = 'tv',
|
TV = 'tv',
|
||||||
Full = 'full',
|
Full = 'full',
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import { GrafanaConfig } from '@grafana/data';
|
|||||||
import { BackendSrv, LocationService } from '@grafana/runtime';
|
import { BackendSrv, LocationService } from '@grafana/runtime';
|
||||||
import { AppChromeService } from 'app/core/components/AppChrome/AppChromeService';
|
import { AppChromeService } from 'app/core/components/AppChrome/AppChromeService';
|
||||||
import { GrafanaContextType } from 'app/core/context/GrafanaContext';
|
import { GrafanaContextType } from 'app/core/context/GrafanaContext';
|
||||||
|
import { KeybindingSrv } from 'app/core/services/keybindingSrv';
|
||||||
|
|
||||||
/** Not sure what this should evolve into, just a starting point */
|
/** Not sure what this should evolve into, just a starting point */
|
||||||
export function getGrafanaContextMock(overrides: Partial<GrafanaContextType> = {}): GrafanaContextType {
|
export function getGrafanaContextMock(overrides: Partial<GrafanaContextType> = {}): GrafanaContextType {
|
||||||
@ -13,6 +14,12 @@ export function getGrafanaContextMock(overrides: Partial<GrafanaContextType> = {
|
|||||||
location: {} as LocationService,
|
location: {} as LocationService,
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
config: {} as GrafanaConfig,
|
config: {} as GrafanaConfig,
|
||||||
|
// eslint-disable-next-line
|
||||||
|
keybindings: {
|
||||||
|
clearAndInitGlobalBindings: jest.fn(),
|
||||||
|
setupDashboardBindings: jest.fn(),
|
||||||
|
setupTimeRangeBindings: jest.fn(),
|
||||||
|
} as any as KeybindingSrv,
|
||||||
...overrides,
|
...overrides,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user