mirror of
https://github.com/grafana/grafana.git
synced 2025-02-13 17:15:40 -06:00
GrafanaContext: Exploring a way to get rid of global scope singletons (#52128)
* Context start * More progress on more generic react context for services * Update * Update Page test * Fixing tests * Moving to core app
This commit is contained in:
parent
4eb0a8a98e
commit
b782d9aa12
@ -3356,9 +3356,7 @@ exports[`better eslint`] = {
|
||||
],
|
||||
"public/app/core/utils/ConfigProvider.tsx:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "2"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "3"]
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "1"]
|
||||
],
|
||||
"public/app/core/utils/acl.ts:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"]
|
||||
|
@ -15,11 +15,12 @@ import { GrafanaApp } from './app';
|
||||
import { AppChrome } from './core/components/AppChrome/AppChrome';
|
||||
import { AppNotificationList } from './core/components/AppNotifications/AppNotificationList';
|
||||
import { NavBar } from './core/components/NavBar/NavBar';
|
||||
import { GrafanaContext } from './core/context/GrafanaContext';
|
||||
import { I18nProvider } from './core/internationalization';
|
||||
import { GrafanaRoute } from './core/navigation/GrafanaRoute';
|
||||
import { RouteDescriptor } from './core/navigation/types';
|
||||
import { contextSrv } from './core/services/context_srv';
|
||||
import { ConfigContext, ThemeProvider } from './core/utils/ConfigProvider';
|
||||
import { ThemeProvider } from './core/utils/ConfigProvider';
|
||||
import { CommandPalette } from './features/commandPalette/CommandPalette';
|
||||
import { LiveConnectionWarning } from './features/live/LiveConnectionWarning';
|
||||
|
||||
@ -99,6 +100,7 @@ export class AppWrapper extends React.Component<AppWrapperProps, AppWrapperState
|
||||
}
|
||||
|
||||
render() {
|
||||
const { app } = this.props;
|
||||
const { ready } = this.state;
|
||||
|
||||
navigationLogger('AppWrapper', false, 'rendering');
|
||||
@ -114,7 +116,7 @@ export class AppWrapper extends React.Component<AppWrapperProps, AppWrapperState
|
||||
<Provider store={store}>
|
||||
<I18nProvider>
|
||||
<ErrorBoundaryAlert style="page">
|
||||
<ConfigContext.Provider value={config}>
|
||||
<GrafanaContext.Provider value={app.context}>
|
||||
<ThemeProvider value={config.theme2}>
|
||||
<KBarProvider
|
||||
actions={[]}
|
||||
@ -147,7 +149,7 @@ export class AppWrapper extends React.Component<AppWrapperProps, AppWrapperState
|
||||
</ModalsProvider>
|
||||
</KBarProvider>
|
||||
</ThemeProvider>
|
||||
</ConfigContext.Provider>
|
||||
</GrafanaContext.Provider>
|
||||
</ErrorBoundaryAlert>
|
||||
</I18nProvider>
|
||||
</Provider>
|
||||
|
@ -42,7 +42,9 @@ import { getStandardTransformers } from 'app/features/transformers/standardTrans
|
||||
import getDefaultMonacoLanguages from '../lib/monaco-languages';
|
||||
|
||||
import { AppWrapper } from './AppWrapper';
|
||||
import { AppChromeService } from './core/components/AppChrome/AppChromeService';
|
||||
import { getAllOptionEditors, getAllStandardFieldConfigs } from './core/components/OptionsUI/registry';
|
||||
import { GrafanaContextType } from './core/context/GrafanaContext';
|
||||
import { interceptLinkClicks } from './core/navigation/patch/interceptLinkClicks';
|
||||
import { ModalManager } from './core/services/ModalManager';
|
||||
import { backendSrv } from './core/services/backend_srv';
|
||||
@ -91,6 +93,8 @@ if (process.env.NODE_ENV === 'development') {
|
||||
}
|
||||
|
||||
export class GrafanaApp {
|
||||
context!: GrafanaContextType;
|
||||
|
||||
async init() {
|
||||
try {
|
||||
setBackendSrv(backendSrv);
|
||||
@ -147,6 +151,13 @@ export class GrafanaApp {
|
||||
// Preload selected app plugins
|
||||
await preloadPlugins(config.pluginsToPreload);
|
||||
|
||||
this.context = {
|
||||
backend: backendSrv,
|
||||
location: locationService,
|
||||
chrome: new AppChromeService(),
|
||||
config,
|
||||
};
|
||||
|
||||
ReactDOM.render(
|
||||
React.createElement(AppWrapper, {
|
||||
app: this,
|
||||
|
@ -4,10 +4,10 @@ import React, { PropsWithChildren } from 'react';
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { useStyles2 } from '@grafana/ui';
|
||||
import { useGrafana } from 'app/core/context/GrafanaContext';
|
||||
|
||||
import { MegaMenu } from '../MegaMenu/MegaMenu';
|
||||
|
||||
import { appChromeService } from './AppChromeService';
|
||||
import { NavToolbar } from './NavToolbar';
|
||||
import { TopSearchBar } from './TopSearchBar';
|
||||
import { TOP_BAR_LEVEL_HEIGHT } from './types';
|
||||
@ -16,7 +16,8 @@ export interface Props extends PropsWithChildren<{}> {}
|
||||
|
||||
export function AppChrome({ children }: Props) {
|
||||
const styles = useStyles2(getStyles);
|
||||
const state = appChromeService.useState();
|
||||
const { chrome } = useGrafana();
|
||||
const state = chrome.useState();
|
||||
|
||||
if (state.chromeless || !config.featureToggles.topnav) {
|
||||
return <main className="main-view">{children} </main>;
|
||||
@ -31,14 +32,12 @@ export function AppChrome({ children }: Props) {
|
||||
sectionNav={state.sectionNav}
|
||||
pageNav={state.pageNav}
|
||||
actions={state.actions}
|
||||
onToggleSearchBar={appChromeService.toggleSearchBar}
|
||||
onToggleMegaMenu={appChromeService.toggleMegaMenu}
|
||||
onToggleSearchBar={chrome.toggleSearchBar}
|
||||
onToggleMegaMenu={chrome.toggleMegaMenu}
|
||||
/>
|
||||
</div>
|
||||
<div className={cx(styles.content, state.searchBarHidden && styles.contentNoSearchBar)}>{children}</div>
|
||||
{state.megaMenuOpen && (
|
||||
<MegaMenu searchBarHidden={state.searchBarHidden} onClose={appChromeService.toggleMegaMenu} />
|
||||
)}
|
||||
{state.megaMenuOpen && <MegaMenu searchBarHidden={state.searchBarHidden} onClose={chrome.toggleMegaMenu} />}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
@ -63,5 +63,3 @@ export class AppChromeService {
|
||||
return useObservable(this.state, this.state.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
export const appChromeService = new AppChromeService();
|
||||
|
@ -1,8 +1,7 @@
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
import { NavModelItem } from '@grafana/data';
|
||||
|
||||
import { appChromeService } from './AppChromeService';
|
||||
import { useGrafana } from 'app/core/context/GrafanaContext';
|
||||
|
||||
export interface AppChromeUpdateProps {
|
||||
pageNav?: NavModelItem;
|
||||
@ -13,8 +12,10 @@ export interface AppChromeUpdateProps {
|
||||
* This is the way core pages and plugins update the breadcrumbs and page toolbar actions
|
||||
*/
|
||||
export const AppChromeUpdate = React.memo<AppChromeUpdateProps>(({ pageNav, actions }: AppChromeUpdateProps) => {
|
||||
const { chrome } = useGrafana();
|
||||
|
||||
useEffect(() => {
|
||||
appChromeService.update({ pageNav, actions });
|
||||
chrome.update({ pageNav, actions });
|
||||
});
|
||||
return null;
|
||||
});
|
||||
|
@ -1,9 +1,11 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock';
|
||||
|
||||
import { NavModelItem } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { GrafanaContext } from 'app/core/context/GrafanaContext';
|
||||
import { configureStore } from 'app/store/configureStore';
|
||||
|
||||
import { PageProps } from '../Page/types';
|
||||
@ -31,15 +33,20 @@ const setup = (props: Partial<PageProps>) => {
|
||||
},
|
||||
];
|
||||
|
||||
const context = getGrafanaContextMock();
|
||||
const store = configureStore();
|
||||
|
||||
return render(
|
||||
const renderResult = render(
|
||||
<Provider store={store}>
|
||||
<Page {...props}>
|
||||
<div data-testid="page-children">Children</div>
|
||||
</Page>
|
||||
<GrafanaContext.Provider value={context}>
|
||||
<Page {...props}>
|
||||
<div data-testid="page-children">Children</div>
|
||||
</Page>
|
||||
</GrafanaContext.Provider>
|
||||
</Provider>
|
||||
);
|
||||
|
||||
return { renderResult, context };
|
||||
};
|
||||
|
||||
describe('Render', () => {
|
||||
@ -68,6 +75,12 @@ describe('Render', () => {
|
||||
expect(screen.getAllByRole('tab').length).toBe(2);
|
||||
});
|
||||
|
||||
it('should update chrome with section and pageNav', async () => {
|
||||
const { context } = setup({ navId: 'child1', pageNav });
|
||||
expect(context.chrome.state.getValue().sectionNav.id).toBe('child1');
|
||||
expect(context.chrome.state.getValue().pageNav).toBe(pageNav);
|
||||
});
|
||||
|
||||
it('should render section nav model based on navId and item page nav', async () => {
|
||||
setup({ navId: 'child1', pageNav });
|
||||
|
||||
|
@ -4,9 +4,8 @@ import React, { useEffect } from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { CustomScrollbar, useStyles2 } from '@grafana/ui';
|
||||
import { useGrafana } from 'app/core/context/GrafanaContext';
|
||||
|
||||
// Components
|
||||
import { appChromeService } from '../AppChrome/AppChromeService';
|
||||
import { Footer } from '../Footer/Footer';
|
||||
import { PageLayoutType, PageType } from '../Page/types';
|
||||
import { usePageNav } from '../Page/usePageNav';
|
||||
@ -31,6 +30,7 @@ export const Page: PageType = ({
|
||||
}) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
const navModel = usePageNav(navId, oldNavProp);
|
||||
const { chrome } = useGrafana();
|
||||
|
||||
usePageTitle(navModel, pageNav);
|
||||
|
||||
@ -38,12 +38,12 @@ export const Page: PageType = ({
|
||||
|
||||
useEffect(() => {
|
||||
if (navModel) {
|
||||
appChromeService.update({
|
||||
chrome.update({
|
||||
sectionNav: navModel.node,
|
||||
...(pageNav && { pageNav }),
|
||||
});
|
||||
}
|
||||
}, [navModel, pageNav]);
|
||||
}, [navModel, pageNav, chrome]);
|
||||
|
||||
return (
|
||||
<div className={cx(styles.wrapper, className)}>
|
||||
|
25
public/app/core/context/GrafanaContext.ts
Normal file
25
public/app/core/context/GrafanaContext.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import React, { useContext } from 'react';
|
||||
|
||||
import { GrafanaConfig } from '@grafana/data';
|
||||
import { LocationService } from '@grafana/runtime/src/services/LocationService';
|
||||
import { BackendSrv } from '@grafana/runtime/src/services/backendSrv';
|
||||
|
||||
import { AppChromeService } from '../components/AppChrome/AppChromeService';
|
||||
|
||||
export interface GrafanaContextType {
|
||||
backend: BackendSrv;
|
||||
location: LocationService;
|
||||
config: GrafanaConfig;
|
||||
chrome: AppChromeService;
|
||||
}
|
||||
|
||||
export const GrafanaContext = React.createContext<GrafanaContextType | undefined>(undefined);
|
||||
|
||||
export function useGrafana(): GrafanaContextType {
|
||||
const context = useContext(GrafanaContext);
|
||||
if (!context) {
|
||||
throw new Error('No GrafanaContext found');
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
return context as GrafanaContextType;
|
||||
}
|
@ -1,8 +1,10 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock';
|
||||
|
||||
import { setEchoSrv } from '@grafana/runtime';
|
||||
|
||||
import { GrafanaContext } from '../context/GrafanaContext';
|
||||
import { Echo } from '../services/echo/Echo';
|
||||
|
||||
import { GrafanaRoute } from './GrafanaRoute';
|
||||
@ -24,7 +26,9 @@ describe('GrafanaRoute', () => {
|
||||
const match = {} as any;
|
||||
|
||||
render(
|
||||
<GrafanaRoute location={location} history={history} match={match} route={{ component: PageComponent } as any} />
|
||||
<GrafanaContext.Provider value={getGrafanaContextMock()}>
|
||||
<GrafanaRoute location={location} history={history} match={match} route={{ component: PageComponent } as any} />
|
||||
</GrafanaContext.Provider>
|
||||
);
|
||||
|
||||
expect(capturedProps.queryParams.query).toBe('hello');
|
||||
|
@ -1,78 +1,76 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
// @ts-ignore
|
||||
import Drop from 'tether-drop';
|
||||
|
||||
import { locationSearchToObject, navigationLogger, reportPageview } from '@grafana/runtime';
|
||||
|
||||
import { appChromeService } from '../components/AppChrome/AppChromeService';
|
||||
import { useGrafana } from '../context/GrafanaContext';
|
||||
import { keybindingSrv } from '../services/keybindingSrv';
|
||||
|
||||
import { GrafanaRouteComponentProps } from './types';
|
||||
import { GrafanaRouteComponentProps, RouteDescriptor } from './types';
|
||||
|
||||
export interface Props extends Omit<GrafanaRouteComponentProps, 'queryParams'> {}
|
||||
|
||||
export class GrafanaRoute extends React.Component<Props> {
|
||||
componentDidMount() {
|
||||
appChromeService.routeMounted(this.props.route);
|
||||
export function GrafanaRoute(props: Props) {
|
||||
const { chrome } = useGrafana();
|
||||
|
||||
this.updateBodyClassNames();
|
||||
this.cleanupDOM();
|
||||
useEffect(() => {
|
||||
chrome.routeMounted(props.route);
|
||||
|
||||
updateBodyClassNames(props.route);
|
||||
cleanupDOM();
|
||||
// unbinds all and re-bind global keybindins
|
||||
keybindingSrv.reset();
|
||||
keybindingSrv.initGlobals();
|
||||
reportPageview();
|
||||
navigationLogger('GrafanaRoute', false, 'Mounted', this.props.match);
|
||||
}
|
||||
navigationLogger('GrafanaRoute', false, 'Mounted', props.match);
|
||||
|
||||
componentDidUpdate(prevProps: Props) {
|
||||
this.cleanupDOM();
|
||||
return () => {
|
||||
navigationLogger('GrafanaRoute', false, 'Unmounted', props.route);
|
||||
updateBodyClassNames(props.route, true);
|
||||
};
|
||||
}, [chrome, props.route, props.match]);
|
||||
|
||||
useEffect(() => {
|
||||
cleanupDOM();
|
||||
reportPageview();
|
||||
navigationLogger('GrafanaRoute', false, 'Updated', this.props, prevProps);
|
||||
}
|
||||
navigationLogger('GrafanaRoute', false, 'Updated', props);
|
||||
});
|
||||
|
||||
componentWillUnmount() {
|
||||
this.updateBodyClassNames(true);
|
||||
navigationLogger('GrafanaRoute', false, 'Unmounted', this.props.route);
|
||||
}
|
||||
navigationLogger('GrafanaRoute', false, 'Rendered', props.route);
|
||||
|
||||
getPageClasses() {
|
||||
return this.props.route.pageClass ? this.props.route.pageClass.split(' ') : [];
|
||||
}
|
||||
return <props.route.component {...props} queryParams={locationSearchToObject(props.location.search)} />;
|
||||
}
|
||||
|
||||
updateBodyClassNames(clear = false) {
|
||||
for (const cls of this.getPageClasses()) {
|
||||
if (clear) {
|
||||
document.body.classList.remove(cls);
|
||||
} else {
|
||||
document.body.classList.add(cls);
|
||||
}
|
||||
function getPageClasses(route: RouteDescriptor) {
|
||||
return route.pageClass ? route.pageClass.split(' ') : [];
|
||||
}
|
||||
|
||||
function updateBodyClassNames(route: RouteDescriptor, clear = false) {
|
||||
for (const cls of getPageClasses(route)) {
|
||||
if (clear) {
|
||||
document.body.classList.remove(cls);
|
||||
} else {
|
||||
document.body.classList.add(cls);
|
||||
}
|
||||
}
|
||||
|
||||
cleanupDOM() {
|
||||
document.body.classList.remove('sidemenu-open--xs');
|
||||
|
||||
// cleanup tooltips
|
||||
const tooltipById = document.getElementById('tooltip');
|
||||
tooltipById?.parentElement?.removeChild(tooltipById);
|
||||
|
||||
const tooltipsByClass = document.querySelectorAll('.tooltip');
|
||||
for (let i = 0; i < tooltipsByClass.length; i++) {
|
||||
const tooltip = tooltipsByClass[i];
|
||||
tooltip.parentElement?.removeChild(tooltip);
|
||||
}
|
||||
|
||||
// cleanup tether-drop
|
||||
for (const drop of Drop.drops) {
|
||||
drop.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props } = this;
|
||||
navigationLogger('GrafanaRoute', false, 'Rendered', props.route);
|
||||
|
||||
const RouteComponent = props.route.component;
|
||||
return <RouteComponent {...props} queryParams={locationSearchToObject(props.location.search)} />;
|
||||
}
|
||||
}
|
||||
|
||||
function cleanupDOM() {
|
||||
document.body.classList.remove('sidemenu-open--xs');
|
||||
|
||||
// cleanup tooltips
|
||||
const tooltipById = document.getElementById('tooltip');
|
||||
tooltipById?.parentElement?.removeChild(tooltipById);
|
||||
|
||||
const tooltipsByClass = document.querySelectorAll('.tooltip');
|
||||
for (let i = 0; i < tooltipsByClass.length; i++) {
|
||||
const tooltip = tooltipsByClass[i];
|
||||
tooltip.parentElement?.removeChild(tooltip);
|
||||
}
|
||||
|
||||
// cleanup tether-drop
|
||||
for (const drop of Drop.drops) {
|
||||
drop.destroy();
|
||||
}
|
||||
}
|
||||
|
@ -1,27 +1,16 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { config, GrafanaBootConfig, ThemeChangedEvent } from '@grafana/runtime';
|
||||
import { ThemeChangedEvent } from '@grafana/runtime';
|
||||
import { ThemeContext } from '@grafana/ui';
|
||||
|
||||
import { appEvents } from '../core';
|
||||
|
||||
export const ConfigContext = React.createContext<GrafanaBootConfig>(config);
|
||||
export const ConfigConsumer = ConfigContext.Consumer;
|
||||
|
||||
export const provideConfig = (component: React.ComponentType<any>) => {
|
||||
const ConfigProvider = (props: any) => (
|
||||
<ConfigContext.Provider value={config}>{React.createElement(component, { ...props })}</ConfigContext.Provider>
|
||||
);
|
||||
return ConfigProvider;
|
||||
};
|
||||
|
||||
export const ThemeProvider = ({ children, value }: { children: React.ReactNode; value: GrafanaTheme2 }) => {
|
||||
const [theme, setTheme] = useState(value);
|
||||
|
||||
useEffect(() => {
|
||||
const sub = appEvents.subscribe(ThemeChangedEvent, (event) => {
|
||||
//config.theme = event.payload;
|
||||
setTheme(event.payload);
|
||||
});
|
||||
|
||||
@ -32,7 +21,7 @@ export const ThemeProvider = ({ children, value }: { children: React.ReactNode;
|
||||
};
|
||||
|
||||
export const provideTheme = (component: React.ComponentType<any>, theme: GrafanaTheme2) => {
|
||||
return provideConfig((props: any) => (
|
||||
<ThemeProvider value={theme}>{React.createElement(component, { ...props })}</ThemeProvider>
|
||||
));
|
||||
return function ThemeProviderWrapper(props: any) {
|
||||
return <ThemeProvider value={theme}>{React.createElement(component, { ...props })}</ThemeProvider>;
|
||||
};
|
||||
};
|
||||
|
@ -4,9 +4,11 @@ import { fromPairs } from 'lodash';
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { Route, Router } from 'react-router-dom';
|
||||
import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock';
|
||||
|
||||
import { DataSourceApi, DataSourceInstanceSettings, DataSourceRef, QueryEditorProps, ScopedVars } from '@grafana/data';
|
||||
import { locationService, setDataSourceSrv, setEchoSrv } from '@grafana/runtime';
|
||||
import { GrafanaContext } from 'app/core/context/GrafanaContext';
|
||||
import { GrafanaRoute } from 'app/core/navigation/GrafanaRoute';
|
||||
import { Echo } from 'app/core/services/echo/Echo';
|
||||
import { configureStore } from 'app/store/configureStore';
|
||||
@ -92,9 +94,11 @@ export function setupExplore(options?: SetupOptions): {
|
||||
|
||||
const { unmount, container } = render(
|
||||
<Provider store={store}>
|
||||
<Router history={locationService.getHistory()}>
|
||||
<Route path="/explore" exact render={(props) => <GrafanaRoute {...props} route={route as any} />} />
|
||||
</Router>
|
||||
<GrafanaContext.Provider value={getGrafanaContextMock()}>
|
||||
<Router history={locationService.getHistory()}>
|
||||
<Route path="/explore" exact render={(props) => <GrafanaRoute {...props} route={route as any} />} />
|
||||
</Router>
|
||||
</GrafanaContext.Provider>
|
||||
</Provider>
|
||||
);
|
||||
|
||||
|
@ -1,9 +1,11 @@
|
||||
import { act, render, screen } from '@testing-library/react';
|
||||
import React, { Component } from 'react';
|
||||
import { Route, Router } from 'react-router-dom';
|
||||
import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock';
|
||||
|
||||
import { AppPlugin, PluginType, AppRootProps, NavModelItem } from '@grafana/data';
|
||||
import { locationService, setEchoSrv } from '@grafana/runtime';
|
||||
import { GrafanaContext } from 'app/core/context/GrafanaContext';
|
||||
import { GrafanaRoute } from 'app/core/navigation/GrafanaRoute';
|
||||
import { Echo } from 'app/core/services/echo/Echo';
|
||||
|
||||
@ -66,7 +68,9 @@ function renderUnderRouter() {
|
||||
|
||||
render(
|
||||
<Router history={locationService.getHistory()}>
|
||||
<Route path="/a/:pluginId" exact render={(props) => <GrafanaRoute {...props} route={route as any} />} />
|
||||
<GrafanaContext.Provider value={getGrafanaContextMock()}>
|
||||
<Route path="/a/:pluginId" exact render={(props) => <GrafanaRoute {...props} route={route as any} />} />
|
||||
</GrafanaContext.Provider>
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
|
18
public/test/mocks/getGrafanaContextMock.ts
Normal file
18
public/test/mocks/getGrafanaContextMock.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { GrafanaConfig } from '@grafana/data';
|
||||
import { BackendSrv, LocationService } from '@grafana/runtime';
|
||||
import { AppChromeService } from 'app/core/components/AppChrome/AppChromeService';
|
||||
import { GrafanaContextType } from 'app/core/context/GrafanaContext';
|
||||
|
||||
/** Not sure what this should evolve into, just a starting point */
|
||||
export function getGrafanaContextMock(overrides: Partial<GrafanaContextType> = {}): GrafanaContextType {
|
||||
return {
|
||||
chrome: new AppChromeService(),
|
||||
// eslint-disable-next-line
|
||||
backend: {} as BackendSrv,
|
||||
// eslint-disable-next-line
|
||||
location: {} as LocationService,
|
||||
// eslint-disable-next-line
|
||||
config: {} as GrafanaConfig,
|
||||
...overrides,
|
||||
};
|
||||
}
|
Loading…
Reference in New Issue
Block a user