mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Echo: mechanism for collecting custom events lazily (#20365)
* Introduce Echo for collecting frontend metrics * Update public/app/core/services/echo/Echo.ts Co-Authored-By: Peter Holmberg <peterholmberg@users.noreply.github.com> * Custom meta when adding event * Rename consumer to backend * Remove buffer from Echo * Minor tweaks * Update package.json * Update public/app/app.ts * Update public/app/app.ts * Collect paint metrics when collecting tti. Remove echoBackendFactory * Update yarn.lock * Move Echo interfaces to runtime * progress on meta and echo * Collect meta analytics events * Move MetaanalyticsBackend to enterprise repo * Fixed unit tests * Removed unused type from test * Fixed issues with chunk loading (reverted index-template changes) * Restored changes * Fixed webpack prod
This commit is contained in:
committed by
Torkel Ödegaard
parent
4b8a50e70b
commit
178bb1d3ab
@@ -256,6 +256,7 @@
|
|||||||
"tether": "1.4.5",
|
"tether": "1.4.5",
|
||||||
"tether-drop": "https://github.com/torkelo/drop/tarball/master",
|
"tether-drop": "https://github.com/torkelo/drop/tarball/master",
|
||||||
"tinycolor2": "1.4.1",
|
"tinycolor2": "1.4.1",
|
||||||
|
"tti-polyfill": "0.2.2",
|
||||||
"xss": "1.0.3"
|
"xss": "1.0.3"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
export * from './services';
|
export * from './services';
|
||||||
export * from './config';
|
export * from './config';
|
||||||
|
export * from './types';
|
||||||
export { loadPluginCss, SystemJS } from './utils/plugin';
|
export { loadPluginCss, SystemJS } from './utils/plugin';
|
||||||
|
export { reportMetaAnalytics } from './utils/analytics';
|
||||||
|
57
packages/grafana-runtime/src/services/EchoSrv.ts
Normal file
57
packages/grafana-runtime/src/services/EchoSrv.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
interface SizeMeta {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EchoMeta {
|
||||||
|
screenSize: SizeMeta;
|
||||||
|
windowSize: SizeMeta;
|
||||||
|
userAgent: string;
|
||||||
|
url?: string;
|
||||||
|
/**
|
||||||
|
* A unique browser session
|
||||||
|
*/
|
||||||
|
sessionId: string;
|
||||||
|
userLogin: string;
|
||||||
|
userId: number;
|
||||||
|
userSignedIn: boolean;
|
||||||
|
ts: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EchoBackend<T extends EchoEvent = any, O = any> {
|
||||||
|
options: O;
|
||||||
|
supportedEvents: EchoEventType[];
|
||||||
|
flush: () => void;
|
||||||
|
addEvent: (event: T) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EchoEvent<T extends EchoEventType = any, P = any> {
|
||||||
|
type: EchoEventType;
|
||||||
|
payload: P;
|
||||||
|
meta: EchoMeta;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum EchoEventType {
|
||||||
|
Performance = 'performance',
|
||||||
|
MetaAnalytics = 'meta-analytics',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EchoSrv {
|
||||||
|
flush(): void;
|
||||||
|
addBackend(backend: EchoBackend): void;
|
||||||
|
addEvent<T extends EchoEvent>(event: Omit<T, 'meta'>, meta?: {}): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
let singletonInstance: EchoSrv;
|
||||||
|
|
||||||
|
export function setEchoSrv(instance: EchoSrv) {
|
||||||
|
singletonInstance = instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getEchoSrv(): EchoSrv {
|
||||||
|
return singletonInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const registerEchoBackend = (backend: EchoBackend) => {
|
||||||
|
getEchoSrv().addBackend(backend);
|
||||||
|
};
|
@@ -2,3 +2,4 @@ export * from './backendSrv';
|
|||||||
export * from './AngularLoader';
|
export * from './AngularLoader';
|
||||||
export * from './dataSourceSrv';
|
export * from './dataSourceSrv';
|
||||||
export * from './LocationSrv';
|
export * from './LocationSrv';
|
||||||
|
export * from './EchoSrv';
|
||||||
|
18
packages/grafana-runtime/src/types/analytics.ts
Normal file
18
packages/grafana-runtime/src/types/analytics.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { EchoEvent, EchoEventType } from '../services/EchoSrv';
|
||||||
|
|
||||||
|
export interface MetaAnalyticsEventPayload {
|
||||||
|
eventName: string;
|
||||||
|
dashboardId?: number;
|
||||||
|
dashboardUid?: string;
|
||||||
|
dashboardName?: string;
|
||||||
|
folderName?: string;
|
||||||
|
panelId?: number;
|
||||||
|
panelName?: string;
|
||||||
|
datasourceName: string;
|
||||||
|
datasourceId?: number;
|
||||||
|
error?: string;
|
||||||
|
duration: number;
|
||||||
|
dataSize?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MetaAnalyticsEvent extends EchoEvent<EchoEventType.MetaAnalytics, MetaAnalyticsEventPayload> {}
|
1
packages/grafana-runtime/src/types/index.ts
Normal file
1
packages/grafana-runtime/src/types/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './analytics';
|
9
packages/grafana-runtime/src/utils/analytics.ts
Normal file
9
packages/grafana-runtime/src/utils/analytics.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { getEchoSrv, EchoEventType } from '../services/EchoSrv';
|
||||||
|
import { MetaAnalyticsEvent, MetaAnalyticsEventPayload } from '../types/analytics';
|
||||||
|
|
||||||
|
export const reportMetaAnalytics = (payload: MetaAnalyticsEventPayload) => {
|
||||||
|
getEchoSrv().addEvent<MetaAnalyticsEvent>({
|
||||||
|
type: EchoEventType.MetaAnalytics,
|
||||||
|
payload,
|
||||||
|
});
|
||||||
|
};
|
@@ -187,7 +187,7 @@ $btn-drag-image: '../img/grab_dark.svg';
|
|||||||
|
|
||||||
$navbar-btn-gicon-brightness: brightness(0.5);
|
$navbar-btn-gicon-brightness: brightness(0.5);
|
||||||
|
|
||||||
$btn-active-box-shadow: 0px 0px 4px rgba(255,120,10,0.5);
|
$btn-active-box-shadow: 0px 0px 4px rgba(255, 120, 10, 0.5);
|
||||||
|
|
||||||
// Forms
|
// Forms
|
||||||
// -------------------------
|
// -------------------------
|
||||||
|
@@ -16,6 +16,8 @@ import 'vendor/angular-other/angular-strap';
|
|||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import angular from 'angular';
|
import angular from 'angular';
|
||||||
import config from 'app/core/config';
|
import config from 'app/core/config';
|
||||||
|
// @ts-ignore
|
||||||
|
import ttiPolyfill from 'tti-polyfill';
|
||||||
// @ts-ignore ignoring this for now, otherwise we would have to extend _ interface with move
|
// @ts-ignore ignoring this for now, otherwise we would have to extend _ interface with move
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { AppEvents, setMarkdownOptions, setLocale } from '@grafana/data';
|
import { AppEvents, setMarkdownOptions, setLocale } from '@grafana/data';
|
||||||
@@ -34,6 +36,10 @@ _.move = (array: [], fromIndex: number, toIndex: number) => {
|
|||||||
import { coreModule, angularModules } from 'app/core/core_module';
|
import { coreModule, angularModules } from 'app/core/core_module';
|
||||||
import { registerAngularDirectives } from 'app/core/core';
|
import { registerAngularDirectives } from 'app/core/core';
|
||||||
import { setupAngularRoutes } from 'app/routes/routes';
|
import { setupAngularRoutes } from 'app/routes/routes';
|
||||||
|
import { setEchoSrv, registerEchoBackend } from '@grafana/runtime';
|
||||||
|
import { Echo } from './core/services/echo/Echo';
|
||||||
|
import { reportPerformance } from './core/services/echo/EchoSrv';
|
||||||
|
import { PerformanceBackend } from './core/services/echo/backends/PerformanceBackend';
|
||||||
|
|
||||||
import 'app/routes/GrafanaCtrl';
|
import 'app/routes/GrafanaCtrl';
|
||||||
import 'app/features/all';
|
import 'app/features/all';
|
||||||
@@ -163,6 +169,26 @@ export class GrafanaApp {
|
|||||||
importPluginModule(modulePath);
|
importPluginModule(modulePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initEchoSrv() {
|
||||||
|
setEchoSrv(new Echo({ debug: process.env.NODE_ENV === 'development' }));
|
||||||
|
|
||||||
|
ttiPolyfill.getFirstConsistentlyInteractive().then((tti: any) => {
|
||||||
|
// Collecting paint metrics first
|
||||||
|
const paintMetrics = performance.getEntriesByType('paint');
|
||||||
|
|
||||||
|
for (const metric of paintMetrics) {
|
||||||
|
reportPerformance(metric.name, Math.round(metric.startTime + metric.duration));
|
||||||
|
}
|
||||||
|
reportPerformance('tti', tti);
|
||||||
|
});
|
||||||
|
|
||||||
|
registerEchoBackend(new PerformanceBackend({}));
|
||||||
|
|
||||||
|
window.addEventListener('DOMContentLoaded', () => {
|
||||||
|
reportPerformance('dcl', Math.round(performance.now()));
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new GrafanaApp();
|
export default new GrafanaApp();
|
||||||
|
@@ -21,6 +21,7 @@ const setup = (propOverrides?: object) => {
|
|||||||
orgCount: 2,
|
orgCount: 2,
|
||||||
orgRole: '',
|
orgRole: '',
|
||||||
orgId: 1,
|
orgId: 1,
|
||||||
|
login: 'hello',
|
||||||
orgName: 'Grafana',
|
orgName: 'Grafana',
|
||||||
timezone: 'UTC',
|
timezone: 'UTC',
|
||||||
helpFlags1: 1,
|
helpFlags1: 1,
|
||||||
|
@@ -9,6 +9,7 @@ export class User {
|
|||||||
orgRole: any;
|
orgRole: any;
|
||||||
orgId: number;
|
orgId: number;
|
||||||
orgName: string;
|
orgName: string;
|
||||||
|
login: string;
|
||||||
orgCount: number;
|
orgCount: number;
|
||||||
timezone: string;
|
timezone: string;
|
||||||
helpFlags1: number;
|
helpFlags1: number;
|
||||||
|
89
public/app/core/services/echo/Echo.ts
Normal file
89
public/app/core/services/echo/Echo.ts
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
import { EchoBackend, EchoMeta, EchoEvent, EchoSrv } from '@grafana/runtime';
|
||||||
|
import { contextSrv } from '../context_srv';
|
||||||
|
|
||||||
|
interface EchoConfig {
|
||||||
|
// How often should metrics be reported
|
||||||
|
flushInterval: number;
|
||||||
|
// Enables debug mode
|
||||||
|
debug: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Echo is a service for collecting events from Grafana client-app
|
||||||
|
* It collects events, distributes them across registered backend and flushes once per configured interval
|
||||||
|
* It's up to the registered backend to decide what to do with a given type of metric
|
||||||
|
*/
|
||||||
|
export class Echo implements EchoSrv {
|
||||||
|
private config: EchoConfig = {
|
||||||
|
flushInterval: 10000, // By default Echo flushes every 10s
|
||||||
|
debug: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
private backends: EchoBackend[] = [];
|
||||||
|
// meta data added to every event collected
|
||||||
|
|
||||||
|
constructor(config?: Partial<EchoConfig>) {
|
||||||
|
this.config = {
|
||||||
|
...this.config,
|
||||||
|
...config,
|
||||||
|
};
|
||||||
|
setInterval(this.flush, this.config.flushInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
logDebug = (...msg: any) => {
|
||||||
|
if (this.config.debug) {
|
||||||
|
// tslint:disable-next-line
|
||||||
|
// console.debug('ECHO:', ...msg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
flush = () => {
|
||||||
|
for (const backend of this.backends) {
|
||||||
|
backend.flush();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
addBackend = (backend: EchoBackend) => {
|
||||||
|
this.logDebug('Adding backend', backend);
|
||||||
|
this.backends.push(backend);
|
||||||
|
};
|
||||||
|
|
||||||
|
addEvent = <T extends EchoEvent>(event: Omit<T, 'meta'>, _meta?: {}) => {
|
||||||
|
const meta = this.getMeta();
|
||||||
|
const _event = {
|
||||||
|
...event,
|
||||||
|
meta: {
|
||||||
|
...meta,
|
||||||
|
..._meta,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const backend of this.backends) {
|
||||||
|
if (backend.supportedEvents.length === 0 || backend.supportedEvents.indexOf(_event.type) > -1) {
|
||||||
|
backend.addEvent(_event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logDebug('Adding event', _event);
|
||||||
|
};
|
||||||
|
|
||||||
|
getMeta = (): EchoMeta => {
|
||||||
|
return {
|
||||||
|
sessionId: '',
|
||||||
|
userId: contextSrv.user.id,
|
||||||
|
userLogin: contextSrv.user.login,
|
||||||
|
userSignedIn: contextSrv.user.isSignedIn,
|
||||||
|
screenSize: {
|
||||||
|
width: window.innerWidth,
|
||||||
|
height: window.innerHeight,
|
||||||
|
},
|
||||||
|
windowSize: {
|
||||||
|
width: window.screen.width,
|
||||||
|
height: window.screen.height,
|
||||||
|
},
|
||||||
|
userAgent: window.navigator.userAgent,
|
||||||
|
ts: performance.now(),
|
||||||
|
url: window.location.href,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
12
public/app/core/services/echo/EchoSrv.ts
Normal file
12
public/app/core/services/echo/EchoSrv.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { getEchoSrv, EchoEventType } from '@grafana/runtime';
|
||||||
|
import { PerformanceEvent } from './backends/PerformanceBackend';
|
||||||
|
|
||||||
|
export const reportPerformance = (metric: string, value: number) => {
|
||||||
|
getEchoSrv().addEvent<PerformanceEvent>({
|
||||||
|
type: EchoEventType.Performance,
|
||||||
|
payload: {
|
||||||
|
metricName: metric,
|
||||||
|
duration: value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
49
public/app/core/services/echo/backends/PerformanceBackend.ts
Normal file
49
public/app/core/services/echo/backends/PerformanceBackend.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { EchoBackend, EchoEvent, EchoEventType } from '@grafana/runtime';
|
||||||
|
|
||||||
|
export interface PerformanceEventPayload {
|
||||||
|
metricName: string;
|
||||||
|
duration: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PerformanceEvent extends EchoEvent<EchoEventType.Performance, PerformanceEventPayload> {}
|
||||||
|
|
||||||
|
export interface PerformanceBackendOptions {
|
||||||
|
url?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Echo's performance metrics consumer
|
||||||
|
* Reports performance metrics to given url (TODO)
|
||||||
|
*/
|
||||||
|
export class PerformanceBackend implements EchoBackend<PerformanceEvent, PerformanceBackendOptions> {
|
||||||
|
private buffer: PerformanceEvent[] = [];
|
||||||
|
supportedEvents = [EchoEventType.Performance];
|
||||||
|
|
||||||
|
constructor(public options: PerformanceBackendOptions) {}
|
||||||
|
|
||||||
|
addEvent = (e: EchoEvent) => {
|
||||||
|
this.buffer.push(e);
|
||||||
|
};
|
||||||
|
|
||||||
|
flush = () => {
|
||||||
|
if (this.buffer.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
metrics: this.buffer,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Currently we don have API for sending the metrics hence loging to console in dev environment
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log('PerformanceBackend flushing:', result);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.buffer = [];
|
||||||
|
|
||||||
|
// TODO: Enable backend request when we have metrics API
|
||||||
|
// if (this.options.url) {
|
||||||
|
// getBackendSrv().post(this.options.url, result);
|
||||||
|
// }
|
||||||
|
};
|
||||||
|
}
|
@@ -1,22 +1,19 @@
|
|||||||
import { PanelQueryRunner } from './PanelQueryRunner';
|
import { PanelQueryRunner } from './PanelQueryRunner';
|
||||||
import { PanelData, DataQueryRequest, dateTime, ScopedVars } from '@grafana/data';
|
import { PanelData, DataQueryRequest, dateTime, ScopedVars } from '@grafana/data';
|
||||||
import { PanelModel } from './PanelModel';
|
import { DashboardModel } from './index';
|
||||||
|
import { setEchoSrv } from '@grafana/runtime';
|
||||||
|
import { Echo } from '../../../core/services/echo/Echo';
|
||||||
|
|
||||||
jest.mock('app/core/services/backend_srv');
|
jest.mock('app/core/services/backend_srv');
|
||||||
|
|
||||||
// Defined within setup functions
|
const dashboardModel = new DashboardModel({
|
||||||
const panelsForCurrentDashboardMock: { [key: number]: PanelModel } = {};
|
panels: [{ id: 1, type: 'graph' }],
|
||||||
|
});
|
||||||
|
|
||||||
jest.mock('app/features/dashboard/services/DashboardSrv', () => ({
|
jest.mock('app/features/dashboard/services/DashboardSrv', () => ({
|
||||||
getDashboardSrv: () => {
|
getDashboardSrv: () => {
|
||||||
return {
|
return {
|
||||||
getCurrent: () => {
|
getCurrent: () => dashboardModel,
|
||||||
return {
|
|
||||||
getPanelById: (id: number) => {
|
|
||||||
return panelsForCurrentDashboardMock[id];
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
@@ -68,6 +65,7 @@ function describeQueryRunnerScenario(description: string, scenarioFn: ScenarioFn
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
setEchoSrv(new Echo());
|
||||||
setupFn();
|
setupFn();
|
||||||
|
|
||||||
const datasource: any = {
|
const datasource: any = {
|
||||||
@@ -103,13 +101,6 @@ function describeQueryRunnerScenario(description: string, scenarioFn: ScenarioFn
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
panelsForCurrentDashboardMock[1] = {
|
|
||||||
id: 1,
|
|
||||||
getQueryRunner: () => {
|
|
||||||
return ctx.runner;
|
|
||||||
},
|
|
||||||
} as PanelModel;
|
|
||||||
|
|
||||||
ctx.events = [];
|
ctx.events = [];
|
||||||
ctx.runner.run(args);
|
ctx.runner.run(args);
|
||||||
});
|
});
|
||||||
|
56
public/app/features/dashboard/state/analyticsProcessor.ts
Normal file
56
public/app/features/dashboard/state/analyticsProcessor.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import { getDashboardSrv } from '../services/DashboardSrv';
|
||||||
|
|
||||||
|
import { PanelData, LoadingState, DataSourceApi } from '@grafana/data';
|
||||||
|
|
||||||
|
import { reportMetaAnalytics, MetaAnalyticsEventPayload } from '@grafana/runtime';
|
||||||
|
|
||||||
|
export function getAnalyticsProcessor(datasource: DataSourceApi) {
|
||||||
|
let done = false;
|
||||||
|
|
||||||
|
return (data: PanelData) => {
|
||||||
|
if (!data.request || done) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.state !== LoadingState.Done && data.state !== LoadingState.Error) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const eventData: MetaAnalyticsEventPayload = {
|
||||||
|
datasourceName: datasource.name,
|
||||||
|
datasourceId: datasource.id,
|
||||||
|
panelId: data.request.panelId,
|
||||||
|
dashboardId: data.request.dashboardId,
|
||||||
|
// app: 'dashboard',
|
||||||
|
// count: 1,
|
||||||
|
dataSize: 0,
|
||||||
|
duration: data.request.endTime - data.request.startTime,
|
||||||
|
eventName: 'data-request',
|
||||||
|
// sessionId: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
// enrich with dashboard info
|
||||||
|
const dashboard = getDashboardSrv().getCurrent();
|
||||||
|
if (dashboard) {
|
||||||
|
eventData.dashboardId = dashboard.id;
|
||||||
|
eventData.dashboardName = dashboard.title;
|
||||||
|
eventData.dashboardUid = dashboard.uid;
|
||||||
|
eventData.folderName = dashboard.meta.folderTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.series.length > 0) {
|
||||||
|
// estimate size
|
||||||
|
eventData.dataSize = data.series.length * data.series[0].length;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.error) {
|
||||||
|
eventData.error = data.error.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
reportMetaAnalytics(eventData);
|
||||||
|
|
||||||
|
// this done check is to make sure we do not double emit events in case
|
||||||
|
// there are multiple responses with done state
|
||||||
|
done = true;
|
||||||
|
};
|
||||||
|
}
|
@@ -10,9 +10,24 @@ import {
|
|||||||
import { Subscriber, Observable, Subscription } from 'rxjs';
|
import { Subscriber, Observable, Subscription } from 'rxjs';
|
||||||
import { runRequest } from './runRequest';
|
import { runRequest } from './runRequest';
|
||||||
import { deepFreeze } from '../../../../test/core/redux/reducerTester';
|
import { deepFreeze } from '../../../../test/core/redux/reducerTester';
|
||||||
|
import { DashboardModel } from './DashboardModel';
|
||||||
|
import { setEchoSrv } from '@grafana/runtime';
|
||||||
|
import { Echo } from '../../../core/services/echo/Echo';
|
||||||
|
|
||||||
jest.mock('app/core/services/backend_srv');
|
jest.mock('app/core/services/backend_srv');
|
||||||
|
|
||||||
|
const dashboardModel = new DashboardModel({
|
||||||
|
panels: [{ id: 1, type: 'graph' }],
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.mock('app/features/dashboard/services/DashboardSrv', () => ({
|
||||||
|
getDashboardSrv: () => {
|
||||||
|
return {
|
||||||
|
getCurrent: () => dashboardModel,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
class ScenarioCtx {
|
class ScenarioCtx {
|
||||||
ds: DataSourceApi;
|
ds: DataSourceApi;
|
||||||
request: DataQueryRequest;
|
request: DataQueryRequest;
|
||||||
@@ -84,6 +99,7 @@ function runRequestScenario(desc: string, fn: (ctx: ScenarioCtx) => void) {
|
|||||||
const ctx = new ScenarioCtx();
|
const ctx = new ScenarioCtx();
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
setEchoSrv(new Echo());
|
||||||
ctx.reset();
|
ctx.reset();
|
||||||
return ctx.setupFn();
|
return ctx.setupFn();
|
||||||
});
|
});
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
// Libraries
|
// Libraries
|
||||||
import { Observable, of, timer, merge, from } from 'rxjs';
|
import { Observable, of, timer, merge, from } from 'rxjs';
|
||||||
import { flatten, map as lodashMap, isArray, isString } from 'lodash';
|
import { flatten, map as lodashMap, isArray, isString } from 'lodash';
|
||||||
import { map, catchError, takeUntil, mapTo, share, finalize } from 'rxjs/operators';
|
import { map, catchError, takeUntil, mapTo, share, finalize, tap } from 'rxjs/operators';
|
||||||
// Utils & Services
|
// Utils & Services
|
||||||
import { getBackendSrv } from 'app/core/services/backend_srv';
|
import { getBackendSrv } from 'app/core/services/backend_srv';
|
||||||
// Types
|
// Types
|
||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
DataFrame,
|
DataFrame,
|
||||||
guessFieldTypes,
|
guessFieldTypes,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
|
import { getAnalyticsProcessor } from './analyticsProcessor';
|
||||||
import { ExpressionDatasourceID, expressionDatasource } from 'app/features/expressions/ExpressionDatasource';
|
import { ExpressionDatasourceID, expressionDatasource } from 'app/features/expressions/ExpressionDatasource';
|
||||||
|
|
||||||
type MapOfResponsePackets = { [str: string]: DataQueryResponse };
|
type MapOfResponsePackets = { [str: string]: DataQueryResponse };
|
||||||
@@ -119,6 +120,7 @@ export function runRequest(datasource: DataSourceApi, request: DataQueryRequest)
|
|||||||
error: processQueryError(err),
|
error: processQueryError(err),
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
|
tap(getAnalyticsProcessor(datasource)),
|
||||||
// finalize is triggered when subscriber unsubscribes
|
// finalize is triggered when subscriber unsubscribes
|
||||||
// This makes sure any still running network requests are cancelled
|
// This makes sure any still running network requests are cancelled
|
||||||
finalize(cancelNetworkRequestsOnUnsubscribe(request)),
|
finalize(cancelNetworkRequestsOnUnsubscribe(request)),
|
||||||
|
@@ -1,2 +1,4 @@
|
|||||||
import app from './app';
|
import app from './app';
|
||||||
|
|
||||||
|
app.initEchoSrv();
|
||||||
app.init();
|
app.init();
|
||||||
|
@@ -141,7 +141,6 @@ export function grafanaAppDirective(
|
|||||||
controller: GrafanaCtrl,
|
controller: GrafanaCtrl,
|
||||||
link: (scope: IRootScopeService & AppEventEmitter, elem: JQuery) => {
|
link: (scope: IRootScopeService & AppEventEmitter, elem: JQuery) => {
|
||||||
const body = $('body');
|
const body = $('body');
|
||||||
|
|
||||||
// see https://github.com/zenorocha/clipboard.js/issues/155
|
// see https://github.com/zenorocha/clipboard.js/issues/155
|
||||||
$.fn.modal.Constructor.prototype.enforceFocus = () => {};
|
$.fn.modal.Constructor.prototype.enforceFocus = () => {};
|
||||||
|
|
||||||
|
File diff suppressed because one or more lines are too long
@@ -81,7 +81,7 @@ module.exports = (env = {}) =>
|
|||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
filename: path.resolve(__dirname, '../../public/views/index.html'),
|
filename: path.resolve(__dirname, '../../public/views/index.html'),
|
||||||
template: path.resolve(__dirname, '../../public/views/index-template.html'),
|
template: path.resolve(__dirname, '../../public/views/index-template.html'),
|
||||||
inject: 'body',
|
inject: false,
|
||||||
chunksSortMode: 'none',
|
chunksSortMode: 'none',
|
||||||
excludeChunks: ['dark', 'light']
|
excludeChunks: ['dark', 'light']
|
||||||
}),
|
}),
|
||||||
|
@@ -77,7 +77,7 @@ module.exports = merge(common, {
|
|||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
filename: path.resolve(__dirname, '../../public/views/index.html'),
|
filename: path.resolve(__dirname, '../../public/views/index.html'),
|
||||||
template: path.resolve(__dirname, '../../public/views/index-template.html'),
|
template: path.resolve(__dirname, '../../public/views/index-template.html'),
|
||||||
inject: 'body',
|
inject: false,
|
||||||
excludeChunks: ['manifest', 'dark', 'light'],
|
excludeChunks: ['manifest', 'dark', 'light'],
|
||||||
chunksSortMode: 'none'
|
chunksSortMode: 'none'
|
||||||
}),
|
}),
|
||||||
|
@@ -20371,6 +20371,11 @@ tsutils@^3.9.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
tslib "^1.8.1"
|
tslib "^1.8.1"
|
||||||
|
|
||||||
|
tti-polyfill@0.2.2:
|
||||||
|
version "0.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/tti-polyfill/-/tti-polyfill-0.2.2.tgz#f7bbf71b13afa9edf60c8bb0d0c05f134e1513b9"
|
||||||
|
integrity sha512-URIoJxvsHThbQEJij29hIBUDHx9UNoBBCQVjy7L8PnzkqY8N6lsAI6h8JrT1Wt2lA0avus/DkuiJxd9qpfCpqw==
|
||||||
|
|
||||||
tty-browserify@0.0.0:
|
tty-browserify@0.0.0:
|
||||||
version "0.0.0"
|
version "0.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6"
|
resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6"
|
||||||
|
Reference in New Issue
Block a user