diff --git a/.betterer.results b/.betterer.results
index b36213f0033..63f14419f75 100644
--- a/.betterer.results
+++ b/.betterer.results
@@ -514,7 +514,7 @@ exports[`better eslint`] = {
[0, 0, 0, "Unexpected any. Specify a different type.", "2"],
[0, 0, 0, "Unexpected any. Specify a different type.", "3"]
],
- "packages/grafana-runtime/src/services/LocationService.ts:5381": [
+ "packages/grafana-runtime/src/services/LocationService.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"],
diff --git a/packages/grafana-runtime/src/services/LocationService.test.ts b/packages/grafana-runtime/src/services/LocationService.test.tsx
similarity index 69%
rename from packages/grafana-runtime/src/services/LocationService.test.ts
rename to packages/grafana-runtime/src/services/LocationService.test.tsx
index c01282b5ed5..0354f8f089b 100644
--- a/packages/grafana-runtime/src/services/LocationService.test.ts
+++ b/packages/grafana-runtime/src/services/LocationService.test.tsx
@@ -1,4 +1,6 @@
-import { locationService } from './LocationService';
+import { renderHook } from '@testing-library/react-hooks';
+
+import { locationService, HistoryWrapper, useLocationService, LocationServiceProvider } from './LocationService';
describe('LocationService', () => {
describe('getSearchObject', () => {
@@ -51,4 +53,15 @@ describe('LocationService', () => {
});
});
});
+
+ describe('hook access', () => {
+ it('can set and access service from a context', () => {
+ const locationServiceLocal = new HistoryWrapper();
+ const wrapper: React.FunctionComponent<{ children: React.ReactNode }> = ({ children }) => (
+ {children}
+ );
+ const hookResult = renderHook(() => useLocationService(), { wrapper });
+ expect(hookResult.result.current).toBe(locationServiceLocal);
+ });
+ });
});
diff --git a/packages/grafana-runtime/src/services/LocationService.ts b/packages/grafana-runtime/src/services/LocationService.tsx
similarity index 86%
rename from packages/grafana-runtime/src/services/LocationService.ts
rename to packages/grafana-runtime/src/services/LocationService.tsx
index af707b0b93e..6a148b15482 100644
--- a/packages/grafana-runtime/src/services/LocationService.ts
+++ b/packages/grafana-runtime/src/services/LocationService.tsx
@@ -1,4 +1,5 @@
import * as H from 'history';
+import React, { useContext } from 'react';
import { deprecationWarning, UrlQueryMap, urlUtil } from '@grafana/data';
import { attachDebugger, createLogger } from '@grafana/ui';
@@ -162,3 +163,21 @@ export const navigationLogger = navigationLog.logger;
// For debugging purposes the location service is attached to global _debug variable
attachDebugger('location', locationService, navigationLog);
+
+// Simple context so the location service can be used without being a singleton
+const LocationServiceContext = React.createContext(undefined);
+
+export function useLocationService(): LocationService {
+ const service = useContext(LocationServiceContext);
+ if (!service) {
+ throw new Error('useLocationService must be used within a LocationServiceProvider');
+ }
+ return service;
+}
+
+export const LocationServiceProvider: React.FC<{ service: LocationService; children: React.ReactNode }> = ({
+ service,
+ children,
+}) => {
+ return {children};
+};
diff --git a/public/app/AppWrapper.tsx b/public/app/AppWrapper.tsx
index 7c0997d7cea..b73839203c2 100644
--- a/public/app/AppWrapper.tsx
+++ b/public/app/AppWrapper.tsx
@@ -4,7 +4,13 @@ import { Provider } from 'react-redux';
import { Router, Redirect, Switch, RouteComponentProps } from 'react-router-dom';
import { CompatRouter, CompatRoute } from 'react-router-dom-v5-compat';
-import { config, locationService, navigationLogger, reportInteraction } from '@grafana/runtime';
+import {
+ config,
+ locationService,
+ LocationServiceProvider,
+ navigationLogger,
+ reportInteraction,
+} from '@grafana/runtime';
import { ErrorBoundaryAlert, GlobalStyles, ModalRoot, PortalContainer, Stack } from '@grafana/ui';
import { getAppRoutes } from 'app/routes/routes';
import { store } from 'app/store/store';
@@ -104,29 +110,31 @@ export class AppWrapper extends Component {
options={{ enableHistory: true, callbacks: { onSelectAction: commandPaletteActionSelected } }}
>
-
-
-
-
-
-
-
-
- {pageBanners.map((Banner, index) => (
-
+
+
+
+
+
+
+
+
+
+ {pageBanners.map((Banner, index) => (
+
+ ))}
+ {ready && this.renderRoutes()}
+
+ {bodyRenderHooks.map((Hook, index) => (
+
))}
- {ready && this.renderRoutes()}
-
- {bodyRenderHooks.map((Hook, index) => (
-
- ))}
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+