mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
User analytics: Add Rudderstack integration (#36567)
* Replace analytics service with Echo backend * Add Rudderstack integration and general pageview and interaction Echo events * Update conf/defaults.ini Co-authored-by: Dan Cech <dcech@grafana.com> * Update packages/grafana-runtime/src/types/analytics.ts Co-authored-by: Dan Cech <dcech@grafana.com> * Update conf/defaults.ini Co-authored-by: Dan Cech <dcech@grafana.com> * Update tests * Force cla check Co-authored-by: Dan Cech <dcech@grafana.com>
This commit is contained in:
parent
cd95e28c30
commit
663a8935f7
@ -196,6 +196,12 @@ google_analytics_ua_id =
|
||||
# Google Tag Manager ID, only enabled if you specify an id here
|
||||
google_tag_manager_id =
|
||||
|
||||
# Rudderstack write key, enabled only if rudderstack_data_plane_url is also set
|
||||
rudderstack_write_key =
|
||||
|
||||
# Rudderstack data plane url, enabled only if rudderstack_write_key is also set
|
||||
rudderstack_data_plane_url =
|
||||
|
||||
#################################### Security ############################
|
||||
[security]
|
||||
# disable creation of admin user on first start of grafana
|
||||
|
@ -7,7 +7,7 @@ export * from './services';
|
||||
export * from './config';
|
||||
export * from './types';
|
||||
export { loadPluginCss, SystemJS, PluginCssOptions } from './utils/plugin';
|
||||
export { reportMetaAnalytics } from './utils/analytics';
|
||||
export { reportMetaAnalytics, reportInteraction, reportPageview } from './utils/analytics';
|
||||
export { logInfo, logDebug, logWarning, logError } from './utils/logging';
|
||||
export {
|
||||
DataSourceWithBackend,
|
||||
|
@ -79,6 +79,8 @@ export enum EchoEventType {
|
||||
Performance = 'performance',
|
||||
MetaAnalytics = 'meta-analytics',
|
||||
Sentry = 'sentry',
|
||||
Pageview = 'pageview',
|
||||
Interaction = 'interaction',
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -70,3 +70,54 @@ export type MetaAnalyticsEventPayload = DashboardViewEventPayload | DataRequestE
|
||||
* @public
|
||||
*/
|
||||
export interface MetaAnalyticsEvent extends EchoEvent<EchoEventType.MetaAnalytics, MetaAnalyticsEventPayload> {}
|
||||
|
||||
/**
|
||||
* Describes the payload of a pageview event.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface PageviewEchoEventPayload {
|
||||
page: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes pageview event with predefined {@link EchoEventType.EchoEventType} type.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type PageviewEchoEvent = EchoEvent<EchoEventType.Pageview, PageviewEchoEventPayload>;
|
||||
|
||||
/**
|
||||
* Describes the payload of a user interaction event.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface InteractionEchoEventPayload {
|
||||
interactionName: string;
|
||||
properties?: Record<string, any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes interaction event with predefined {@link EchoEventType.EchoEventType} type.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type InteractionEchoEvent = EchoEvent<EchoEventType.Interaction, InteractionEchoEventPayload>;
|
||||
|
||||
/**
|
||||
* Pageview event typeguard.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const isPageviewEvent = (event: EchoEvent): event is PageviewEchoEvent => {
|
||||
return Boolean(event.payload.page);
|
||||
};
|
||||
|
||||
/**
|
||||
* Interaction event typeguard.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const isInteractionEvent = (event: EchoEvent): event is InteractionEchoEvent => {
|
||||
return Boolean(event.payload.interactionName);
|
||||
};
|
||||
|
@ -1,5 +1,12 @@
|
||||
import { getEchoSrv, EchoEventType } from '../services/EchoSrv';
|
||||
import { MetaAnalyticsEvent, MetaAnalyticsEventPayload } from '../types/analytics';
|
||||
import {
|
||||
InteractionEchoEvent,
|
||||
MetaAnalyticsEvent,
|
||||
MetaAnalyticsEventPayload,
|
||||
PageviewEchoEvent,
|
||||
} from '../types/analytics';
|
||||
import { locationService } from '../services';
|
||||
import { config } from '../config';
|
||||
|
||||
/**
|
||||
* Helper function to report meta analytics to the {@link EchoSrv}.
|
||||
@ -12,3 +19,34 @@ export const reportMetaAnalytics = (payload: MetaAnalyticsEventPayload) => {
|
||||
payload,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper function to report pageview events to the {@link EchoSrv}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const reportPageview = () => {
|
||||
const location = locationService.getLocation();
|
||||
const page = `${config.appSubUrl ?? ''}${location.pathname}${location.search}${location.hash}`;
|
||||
getEchoSrv().addEvent<PageviewEchoEvent>({
|
||||
type: EchoEventType.Pageview,
|
||||
payload: {
|
||||
page,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper function to report interaction events to the {@link EchoSrv}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const reportInteraction = (interactionName: string, properties?: Record<string, any>) => {
|
||||
getEchoSrv().addEvent<InteractionEchoEvent>({
|
||||
type: EchoEventType.Interaction,
|
||||
payload: {
|
||||
interactionName,
|
||||
properties,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
@ -209,6 +209,8 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *models.ReqContext) (map[string]i
|
||||
"sigV4AuthEnabled": setting.SigV4AuthEnabled,
|
||||
"exploreEnabled": setting.ExploreEnabled,
|
||||
"googleAnalyticsId": setting.GoogleAnalyticsId,
|
||||
"rudderstackWriteKey": setting.RudderstackWriteKey,
|
||||
"rudderstackDataPlaneUrl": setting.RudderstackDataPlaneUrl,
|
||||
"disableLoginForm": setting.DisableLoginForm,
|
||||
"disableUserSignUp": !setting.AllowUserSignUp,
|
||||
"loginHint": setting.LoginHint,
|
||||
|
@ -148,8 +148,10 @@ var (
|
||||
appliedEnvOverrides []string
|
||||
|
||||
// analytics
|
||||
GoogleAnalyticsId string
|
||||
GoogleTagManagerId string
|
||||
GoogleAnalyticsId string
|
||||
GoogleTagManagerId string
|
||||
RudderstackDataPlaneUrl string
|
||||
RudderstackWriteKey string
|
||||
|
||||
// LDAP
|
||||
LDAPEnabled bool
|
||||
@ -894,6 +896,8 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error {
|
||||
cfg.CheckForUpdates = analytics.Key("check_for_updates").MustBool(true)
|
||||
GoogleAnalyticsId = analytics.Key("google_analytics_ua_id").String()
|
||||
GoogleTagManagerId = analytics.Key("google_tag_manager_id").String()
|
||||
RudderstackWriteKey = analytics.Key("rudderstack_write_key").String()
|
||||
RudderstackDataPlaneUrl = analytics.Key("rudderstack_data_plane_url").String()
|
||||
cfg.ReportingEnabled = analytics.Key("reporting_enabled").MustBool(true)
|
||||
cfg.ReportingDistributor = analytics.Key("reporting_distributor").MustString("grafana-labs")
|
||||
if len(cfg.ReportingDistributor) >= 100 {
|
||||
|
@ -47,6 +47,8 @@ import { getTimeSrv } from './features/dashboard/services/TimeSrv';
|
||||
import { getVariablesUrlParams } from './features/variables/getAllVariableValuesForUrl';
|
||||
import getDefaultMonacoLanguages from '../lib/monaco-languages';
|
||||
import { contextSrv } from './core/services/context_srv';
|
||||
import { GAEchoBackend } from './core/services/echo/backends/analytics/GABackend';
|
||||
import { RudderstackBackend } from './core/services/echo/backends/analytics/RudderstackBackend';
|
||||
|
||||
// add move to lodash for backward compatabilty with plugins
|
||||
// @ts-ignore
|
||||
@ -161,6 +163,24 @@ function initEchoSrv() {
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if ((config as any).googleAnalyticsId) {
|
||||
registerEchoBackend(
|
||||
new GAEchoBackend({
|
||||
googleAnalyticsId: (config as any).googleAnalyticsId,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if ((config as any).rudderstackWriteKey && (config as any).rudderstackDataPlaneUrl) {
|
||||
registerEchoBackend(
|
||||
new RudderstackBackend({
|
||||
writeKey: (config as any).rudderstackWriteKey,
|
||||
dataPlaneUrl: (config as any).rudderstackDataPlaneUrl,
|
||||
user: config.bootData.user,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function addClassIfNoOverlayScrollbar() {
|
||||
|
@ -1,11 +1,16 @@
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { GrafanaRoute } from './GrafanaRoute';
|
||||
import { setEchoSrv } from '@grafana/runtime';
|
||||
import { Echo } from '../services/echo/Echo';
|
||||
|
||||
describe('GrafanaRoute', () => {
|
||||
beforeEach(() => {
|
||||
setEchoSrv(new Echo());
|
||||
});
|
||||
|
||||
it('Parses search', () => {
|
||||
let capturedProps: any;
|
||||
|
||||
const PageComponent = (props: any) => {
|
||||
capturedProps = props;
|
||||
return <div />;
|
||||
|
@ -2,9 +2,8 @@ import React from 'react';
|
||||
// @ts-ignore
|
||||
import Drop from 'tether-drop';
|
||||
import { GrafanaRouteComponentProps } from './types';
|
||||
import { locationSearchToObject, navigationLogger } from '@grafana/runtime';
|
||||
import { locationSearchToObject, navigationLogger, reportPageview } from '@grafana/runtime';
|
||||
import { keybindingSrv } from '../services/keybindingSrv';
|
||||
import { analyticsService } from '../services/analytics';
|
||||
|
||||
export interface Props extends Omit<GrafanaRouteComponentProps, 'queryParams'> {}
|
||||
|
||||
@ -15,13 +14,13 @@ export class GrafanaRoute extends React.Component<Props> {
|
||||
// unbinds all and re-bind global keybindins
|
||||
keybindingSrv.reset();
|
||||
keybindingSrv.initGlobals();
|
||||
analyticsService.track();
|
||||
reportPageview();
|
||||
navigationLogger('GrafanaRoute', false, 'Mounted', this.props.match);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Props) {
|
||||
this.cleanupDOM();
|
||||
analyticsService.track();
|
||||
reportPageview();
|
||||
navigationLogger('GrafanaRoute', false, 'Updated', this.props, prevProps);
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,6 @@ import './alert_srv';
|
||||
import './util_srv';
|
||||
import './context_srv';
|
||||
import './timer';
|
||||
import './analytics';
|
||||
import './popover_srv';
|
||||
import './segment_srv';
|
||||
import './backend_srv';
|
||||
|
@ -1,52 +0,0 @@
|
||||
import $ from 'jquery';
|
||||
import config from 'app/core/config';
|
||||
import { locationService } from '@grafana/runtime';
|
||||
|
||||
export class Analytics {
|
||||
private gaId?: string;
|
||||
private ga?: any;
|
||||
|
||||
constructor() {
|
||||
this.track = this.track.bind(this);
|
||||
this.gaId = (config as any).googleAnalyticsId;
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
if (!this.gaId) {
|
||||
return;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: 'https://www.google-analytics.com/analytics.js',
|
||||
dataType: 'script',
|
||||
cache: true,
|
||||
});
|
||||
|
||||
const ga = ((window as any).ga =
|
||||
(window as any).ga ||
|
||||
// this had the equivalent of `eslint-disable-next-line prefer-arrow/prefer-arrow-functions`
|
||||
function () {
|
||||
(ga.q = ga.q || []).push(arguments);
|
||||
});
|
||||
ga.l = +new Date();
|
||||
ga('create', (config as any).googleAnalyticsId, 'auto');
|
||||
ga('set', 'anonymizeIp', true);
|
||||
this.ga = ga;
|
||||
return ga;
|
||||
}
|
||||
|
||||
track() {
|
||||
if (!this.ga) {
|
||||
return;
|
||||
}
|
||||
|
||||
const location = locationService.getLocation();
|
||||
const track = { page: `${config.appSubUrl ?? ''}${location.pathname}${location.search}${location.hash}` };
|
||||
|
||||
this.ga('set', track);
|
||||
this.ga('send', 'pageview');
|
||||
}
|
||||
}
|
||||
|
||||
export const analyticsService = new Analytics();
|
@ -1,5 +1,6 @@
|
||||
import { EchoBackend, EchoMeta, EchoEvent, EchoSrv } from '@grafana/runtime';
|
||||
import { contextSrv } from '../context_srv';
|
||||
import { echoLog } from './utils';
|
||||
|
||||
interface EchoConfig {
|
||||
// How often should metrics be reported
|
||||
@ -30,13 +31,6 @@ export class Echo implements EchoSrv {
|
||||
setInterval(this.flush, this.config.flushInterval);
|
||||
}
|
||||
|
||||
logDebug = (...msg: any) => {
|
||||
if (this.config.debug) {
|
||||
// eslint-disable-next-line
|
||||
// console.debug('ECHO:', ...msg);
|
||||
}
|
||||
};
|
||||
|
||||
flush = () => {
|
||||
for (const backend of this.backends) {
|
||||
backend.flush();
|
||||
@ -44,7 +38,7 @@ export class Echo implements EchoSrv {
|
||||
};
|
||||
|
||||
addBackend = (backend: EchoBackend) => {
|
||||
this.logDebug('Adding backend', backend);
|
||||
echoLog('Adding backend', false, backend);
|
||||
this.backends.push(backend);
|
||||
};
|
||||
|
||||
@ -63,8 +57,7 @@ export class Echo implements EchoSrv {
|
||||
backend.addEvent(_event);
|
||||
}
|
||||
}
|
||||
|
||||
this.logDebug('Adding event', _event);
|
||||
echoLog('Reporting event', false, _event);
|
||||
};
|
||||
|
||||
getMeta = (): EchoMeta => {
|
||||
|
@ -0,0 +1,43 @@
|
||||
import $ from 'jquery';
|
||||
import { EchoBackend, EchoEventType, PageviewEchoEvent } from '@grafana/runtime';
|
||||
|
||||
export interface GAEchoBackendOptions {
|
||||
googleAnalyticsId: string;
|
||||
debug?: boolean;
|
||||
}
|
||||
|
||||
export class GAEchoBackend implements EchoBackend<PageviewEchoEvent, GAEchoBackendOptions> {
|
||||
supportedEvents = [EchoEventType.Pageview];
|
||||
|
||||
constructor(public options: GAEchoBackendOptions) {
|
||||
const url = `https://www.google-analytics.com/analytics${options.debug ? '_debug' : ''}.js`;
|
||||
|
||||
$.ajax({
|
||||
url,
|
||||
dataType: 'script',
|
||||
cache: true,
|
||||
});
|
||||
|
||||
const ga = ((window as any).ga =
|
||||
(window as any).ga ||
|
||||
// this had the equivalent of `eslint-disable-next-line prefer-arrow/prefer-arrow-functions`
|
||||
function () {
|
||||
(ga.q = ga.q || []).push(arguments);
|
||||
});
|
||||
ga.l = +new Date();
|
||||
ga('create', options.googleAnalyticsId, 'auto');
|
||||
ga('set', 'anonymizeIp', true);
|
||||
}
|
||||
|
||||
addEvent = (e: PageviewEchoEvent) => {
|
||||
if (!(window as any).ga) {
|
||||
return;
|
||||
}
|
||||
|
||||
(window as any).ga('set', { page: e.payload.page });
|
||||
(window as any).ga('send', 'pageview');
|
||||
};
|
||||
|
||||
// Not using Echo buffering, addEvent above sends events to GA as soon as they appear
|
||||
flush = () => {};
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
import $ from 'jquery';
|
||||
import { EchoBackend, EchoEventType, isInteractionEvent, isPageviewEvent, PageviewEchoEvent } from '@grafana/runtime';
|
||||
import { User } from '../sentry/types';
|
||||
|
||||
export interface RudderstackBackendOptions {
|
||||
writeKey: string;
|
||||
dataPlaneUrl: string;
|
||||
user?: User;
|
||||
}
|
||||
|
||||
export class RudderstackBackend implements EchoBackend<PageviewEchoEvent, RudderstackBackendOptions> {
|
||||
supportedEvents = [EchoEventType.Pageview, EchoEventType.Interaction];
|
||||
|
||||
constructor(public options: RudderstackBackendOptions) {
|
||||
const url = `https://cdn.rudderlabs.com/v1/rudder-analytics.min.js`;
|
||||
|
||||
$.ajax({
|
||||
url,
|
||||
dataType: 'script',
|
||||
cache: true,
|
||||
});
|
||||
|
||||
const rds = ((window as any).rudderanalytics = []);
|
||||
|
||||
var methods = [
|
||||
'load',
|
||||
'page',
|
||||
'track',
|
||||
'identify',
|
||||
'alias',
|
||||
'group',
|
||||
'ready',
|
||||
'reset',
|
||||
'getAnonymousId',
|
||||
'setAnonymousId',
|
||||
];
|
||||
|
||||
for (let i = 0; i < methods.length; i++) {
|
||||
const method = methods[i];
|
||||
(rds as Record<string, any>)[method] = (function (methodName) {
|
||||
return function () {
|
||||
// @ts-ignore
|
||||
rds.push([methodName].concat(Array.prototype.slice.call(arguments)));
|
||||
};
|
||||
})(method);
|
||||
}
|
||||
|
||||
(rds as any).load(options.writeKey, options.dataPlaneUrl);
|
||||
|
||||
if (options.user) {
|
||||
(rds as any).identify(String(options.user.id), {
|
||||
email: options.user.email,
|
||||
orgId: options.user.orgId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
addEvent = (e: PageviewEchoEvent) => {
|
||||
if (!(window as any).rudderanalytics) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isPageviewEvent(e)) {
|
||||
(window as any).rudderanalytics.page();
|
||||
}
|
||||
|
||||
if (isInteractionEvent(e)) {
|
||||
(window as any).rudderanalytics.track(e.payload.interactionName, e.payload.properties);
|
||||
}
|
||||
};
|
||||
|
||||
// Not using Echo buffering, addEvent above sends events to GA as soon as they appear
|
||||
flush = () => {};
|
||||
}
|
@ -35,6 +35,7 @@ describe('SentryEchoBackend', () => {
|
||||
user: {
|
||||
email: 'darth.vader@sith.glx',
|
||||
id: 504,
|
||||
orgId: 1,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -11,4 +11,5 @@ export type SentryEchoEvent = EchoEvent<EchoEventType.Sentry, SentryEvent>;
|
||||
export interface User {
|
||||
email: string;
|
||||
id: number;
|
||||
orgId: number;
|
||||
}
|
||||
|
7
public/app/core/services/echo/utils.ts
Normal file
7
public/app/core/services/echo/utils.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { attachDebugger, createLogger } from '@grafana/ui';
|
||||
|
||||
/** @internal */
|
||||
export const echoLogger = createLogger('EchoSrv');
|
||||
export const echoLog = echoLogger.logger;
|
||||
|
||||
attachDebugger('echo', undefined, echoLogger);
|
@ -3,7 +3,7 @@ import { connect, MapDispatchToProps } from 'react-redux';
|
||||
import { css, cx, keyframes } from '@emotion/css';
|
||||
import { chain, cloneDeep, defaults, find, sortBy } from 'lodash';
|
||||
import tinycolor from 'tinycolor2';
|
||||
import { locationService } from '@grafana/runtime';
|
||||
import { locationService, reportInteraction } from '@grafana/runtime';
|
||||
import { Icon, IconButton, styleMixins, useStyles } from '@grafana/ui';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
@ -146,13 +146,22 @@ export const AddPanelWidgetUnconnected: React.FC<Props> = ({ panel, dashboard })
|
||||
) : (
|
||||
<div className={styles.actionsWrapper}>
|
||||
<div className={cx(styles.actionsRow, styles.columnGap)}>
|
||||
<div onClick={() => onCreateNewPanel()} aria-label={selectors.pages.AddDashboard.addNewPanel}>
|
||||
<div
|
||||
onClick={() => {
|
||||
reportInteraction('Create new panel');
|
||||
onCreateNewPanel();
|
||||
}}
|
||||
aria-label={selectors.pages.AddDashboard.addNewPanel}
|
||||
>
|
||||
<Icon name="file-blank" size="xl" />
|
||||
Add an empty panel
|
||||
</div>
|
||||
<div
|
||||
className={styles.rowGap}
|
||||
onClick={onCreateNewRow}
|
||||
onClick={() => {
|
||||
reportInteraction('Create new row');
|
||||
onCreateNewRow();
|
||||
}}
|
||||
aria-label={selectors.pages.AddDashboard.addNewRow}
|
||||
>
|
||||
<Icon name="wrap-text" size="xl" />
|
||||
@ -160,12 +169,24 @@ export const AddPanelWidgetUnconnected: React.FC<Props> = ({ panel, dashboard })
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.actionsRow}>
|
||||
<div onClick={() => setAddPanelView(true)} aria-label={selectors.pages.AddDashboard.addNewPanelLibrary}>
|
||||
<div
|
||||
onClick={() => {
|
||||
reportInteraction('Add a panel from the panel library');
|
||||
setAddPanelView(true);
|
||||
}}
|
||||
aria-label={selectors.pages.AddDashboard.addNewPanelLibrary}
|
||||
>
|
||||
<Icon name="book-open" size="xl" />
|
||||
Add a panel from the panel library
|
||||
</div>
|
||||
{copiedPanelPlugins.length === 1 && (
|
||||
<div className={styles.rowGap} onClick={() => onPasteCopiedPanel(copiedPanelPlugins[0])}>
|
||||
<div
|
||||
className={styles.rowGap}
|
||||
onClick={() => {
|
||||
reportInteraction('Paste panel from clipboard');
|
||||
onPasteCopiedPanel(copiedPanelPlugins[0]);
|
||||
}}
|
||||
>
|
||||
<Icon name="clipboard-alt" size="xl" />
|
||||
Paste panel from clipboard
|
||||
</div>
|
||||
|
@ -3,7 +3,7 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import Wrapper from './Wrapper';
|
||||
import { configureStore } from '../../store/configureStore';
|
||||
import { Provider } from 'react-redux';
|
||||
import { locationService, setDataSourceSrv } from '@grafana/runtime';
|
||||
import { locationService, setDataSourceSrv, setEchoSrv } from '@grafana/runtime';
|
||||
import {
|
||||
ArrayDataFrame,
|
||||
DataQueryResponse,
|
||||
@ -26,6 +26,7 @@ import { splitOpen } from './state/main';
|
||||
import { Route, Router } from 'react-router-dom';
|
||||
import { GrafanaRoute } from 'app/core/navigation/GrafanaRoute';
|
||||
import { initialUserState } from '../profile/state/reducers';
|
||||
import { Echo } from 'app/core/services/echo/Echo';
|
||||
|
||||
type Mock = jest.Mock;
|
||||
|
||||
@ -265,6 +266,7 @@ type SetupOptions = {
|
||||
datasources?: DatasourceSetup[];
|
||||
query?: any;
|
||||
};
|
||||
|
||||
function setup(options?: SetupOptions): { datasources: { [name: string]: DataSourceApi }; store: EnhancedStore } {
|
||||
// Clear this up otherwise it persists data source selection
|
||||
// TODO: probably add test for that too
|
||||
@ -296,6 +298,7 @@ function setup(options?: SetupOptions): { datasources: { [name: string]: DataSou
|
||||
return intervals;
|
||||
},
|
||||
} as any);
|
||||
setEchoSrv(new Echo());
|
||||
|
||||
const store = configureStore();
|
||||
store.getState().user = {
|
||||
|
@ -6,8 +6,9 @@ import { importAppPlugin } from './plugin_loader';
|
||||
import { getMockPlugin } from './__mocks__/pluginMocks';
|
||||
import { AppPlugin, PluginType, AppRootProps, NavModelItem } from '@grafana/data';
|
||||
import { Route, Router } from 'react-router-dom';
|
||||
import { locationService } from '@grafana/runtime';
|
||||
import { locationService, setEchoSrv } from '@grafana/runtime';
|
||||
import { GrafanaRoute } from 'app/core/navigation/GrafanaRoute';
|
||||
import { Echo } from 'app/core/services/echo/Echo';
|
||||
|
||||
jest.mock('./PluginSettingsCache', () => ({
|
||||
getPluginSettings: jest.fn(),
|
||||
@ -70,6 +71,7 @@ function renderUnderRouter() {
|
||||
describe('AppRootPage', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
setEchoSrv(new Echo());
|
||||
});
|
||||
|
||||
it('should not mount plugin twice if nav is changed', async () => {
|
||||
|
Loading…
Reference in New Issue
Block a user