From 74c65eca26956fee747224f4eb52c6837562909e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 3 Nov 2020 13:08:54 +0100 Subject: [PATCH] EventBus: Introduces new event bus with emitter backward compatible interface (#27564) * updated * Experimenting with event bus with legacy support * Before switch to emitter * EventBus & Emitter unification * Everything using new EventBus * Making progress * Fixing merge issues * Final merge issues * Updated * Updates * Fix * Updated * Update * Update * Rename methods to publish and subscribe * Ts fixes * Updated * updated * fixing doc warnigns * removed unused file --- packages/grafana-data/package.json | 1 + .../grafana-data/src/events/EventBus.test.ts | 173 ++++++++++++++++++ packages/grafana-data/src/events/EventBus.ts | 85 +++++++++ .../utils.ts => events/eventFactory.ts} | 6 +- packages/grafana-data/src/events/index.ts | 3 + packages/grafana-data/src/events/types.ts | 115 ++++++++++++ packages/grafana-data/src/index.ts | 1 + packages/grafana-data/src/types/appEvents.ts | 13 -- packages/grafana-data/src/types/index.ts | 8 +- .../grafana-data/src/types/legacyEvents.ts | 47 +++++ packages/grafana-data/src/types/panel.ts | 4 + .../grafana-data/src/types/panelEvents.ts | 28 --- public/app/core/app_events.ts | 4 +- public/app/core/core.ts | 2 - public/app/core/services/backend_srv.ts | 3 +- public/app/core/services/keybindingSrv.ts | 4 +- public/app/core/specs/backend_srv.test.ts | 9 +- public/app/core/specs/emitter.test.ts | 67 ------- public/app/core/utils/emitter.ts | 101 ---------- .../DashboardSettings/SettingsCtrl.ts | 3 +- .../VersionHistory/HistoryListCtrl.ts | 4 +- .../__snapshots__/DashboardPage.test.tsx.snap | 28 +-- .../dashboard/dashgrid/PanelChrome.tsx | 9 +- .../__snapshots__/DashboardGrid.test.tsx.snap | 48 ++--- .../dashboard/panel_editor/QueryEditorRow.tsx | 4 +- .../dashboard/state/DashboardModel.ts | 7 +- .../features/dashboard/state/PanelModel.ts | 7 +- .../dashboard/state/PanelQueryRunner.ts | 2 +- public/app/features/explore/Explore.tsx | 7 +- public/app/features/explore/QueryEditor.tsx | 6 +- public/app/features/explore/QueryRow.test.tsx | 4 +- public/app/features/explore/QueryRow.tsx | 4 +- public/app/features/explore/QueryRows.tsx | 4 +- .../__snapshots__/Explore.test.tsx.snap | 2 +- .../app/features/explore/state/actionTypes.ts | 4 +- .../features/explore/state/actions.test.ts | 5 +- public/app/features/explore/state/actions.ts | 4 +- public/app/features/explore/state/reducers.ts | 4 +- public/app/features/panel/panel_ctrl.ts | 5 +- .../panel/bargauge/BarGaugePanel.test.tsx | 1 + public/app/plugins/panel/graph/graph.ts | 12 +- .../app/plugins/panel/graph/graph_tooltip.ts | 6 +- .../plugins/panel/graph/specs/graph.test.ts | 4 +- public/app/plugins/panel/heatmap/rendering.ts | 14 +- public/app/plugins/panel/text/TextPanel.tsx | 3 + public/app/routes/GrafanaCtrl.ts | 2 - public/app/types/events.ts | 10 +- public/app/types/explore.ts | 5 +- yarn.lock | 5 + 49 files changed, 556 insertions(+), 341 deletions(-) create mode 100644 packages/grafana-data/src/events/EventBus.test.ts create mode 100644 packages/grafana-data/src/events/EventBus.ts rename packages/grafana-data/src/{types/utils.ts => events/eventFactory.ts} (65%) create mode 100644 packages/grafana-data/src/events/index.ts create mode 100644 packages/grafana-data/src/events/types.ts delete mode 100644 packages/grafana-data/src/types/appEvents.ts create mode 100644 packages/grafana-data/src/types/legacyEvents.ts delete mode 100644 packages/grafana-data/src/types/panelEvents.ts delete mode 100644 public/app/core/specs/emitter.test.ts delete mode 100644 public/app/core/utils/emitter.ts diff --git a/packages/grafana-data/package.json b/packages/grafana-data/package.json index 621ae164939..f8d8a54c759 100644 --- a/packages/grafana-data/package.json +++ b/packages/grafana-data/package.json @@ -25,6 +25,7 @@ "@braintree/sanitize-url": "4.0.0", "@types/d3-interpolate": "^1.3.1", "apache-arrow": "0.16.0", + "eventemitter3": "4.0.7", "lodash": "4.17.19", "rxjs": "6.6.3", "xss": "1.0.6" diff --git a/packages/grafana-data/src/events/EventBus.test.ts b/packages/grafana-data/src/events/EventBus.test.ts new file mode 100644 index 00000000000..458a74fa392 --- /dev/null +++ b/packages/grafana-data/src/events/EventBus.test.ts @@ -0,0 +1,173 @@ +import { EventBusSrv } from './EventBus'; +import { BusEventWithPayload } from './types'; +import { eventFactory } from './eventFactory'; + +interface LoginEventPayload { + logins: number; +} + +interface HelloEventPayload { + hellos: number; +} + +class LoginEvent extends BusEventWithPayload { + static type = 'login-event'; +} + +class HelloEvent extends BusEventWithPayload { + static type = 'hello-event'; +} + +type LegacyEventPayload = [string, string]; + +export const legacyEvent = eventFactory('legacy-event'); + +class AlertSuccessEvent extends BusEventWithPayload { + static type = 'legacy-event'; +} + +describe('EventBus', () => { + it('Can create events', () => { + expect(new LoginEvent({ logins: 1 }).type).toBe('login-event'); + }); + + it('Can subscribe specific event', () => { + const bus = new EventBusSrv(); + const events: LoginEvent[] = []; + + bus.subscribe(LoginEvent, event => { + events.push(event); + }); + + bus.publish(new LoginEvent({ logins: 10 })); + bus.publish(new HelloEvent({ hellos: 10 })); + + expect(events[0].payload.logins).toBe(10); + expect(events.length).toBe(1); + }); + + describe('Legacy emitter behavior', () => { + it('Supports legacy events', () => { + const bus = new EventBusSrv(); + const events: any = []; + const handler = (event: LegacyEventPayload) => { + events.push(event); + }; + + bus.on(legacyEvent, handler); + bus.emit(legacyEvent, ['hello', 'hello2']); + + bus.off(legacyEvent, handler); + bus.emit(legacyEvent, ['hello', 'hello2']); + + expect(events.length).toEqual(1); + expect(events[0]).toEqual(['hello', 'hello2']); + }); + + it('Interoperability with legacy events', () => { + const bus = new EventBusSrv(); + const legacyEvents: any = []; + const newEvents: any = []; + + bus.on(legacyEvent, event => { + legacyEvents.push(event); + }); + + bus.subscribe(AlertSuccessEvent, event => { + newEvents.push(event); + }); + + bus.emit(legacyEvent, ['legacy', 'params']); + bus.publish(new AlertSuccessEvent(['new', 'event'])); + + expect(legacyEvents).toEqual([ + ['legacy', 'params'], + ['new', 'event'], + ]); + + expect(newEvents).toEqual([ + { + type: 'legacy-event', + payload: ['legacy', 'params'], + }, + { + type: 'legacy-event', + payload: ['new', 'event'], + }, + ]); + }); + + it('should notfiy subscribers', () => { + const bus = new EventBusSrv(); + let sub1Called = false; + let sub2Called = false; + + bus.on(legacyEvent, () => { + sub1Called = true; + }); + bus.on(legacyEvent, () => { + sub2Called = true; + }); + + bus.emit(legacyEvent, null); + + expect(sub1Called).toBe(true); + expect(sub2Called).toBe(true); + }); + + it('when subscribing twice', () => { + const bus = new EventBusSrv(); + let sub1Called = 0; + + function handler() { + sub1Called += 1; + } + + bus.on(legacyEvent, handler); + bus.on(legacyEvent, handler); + + bus.emit(legacyEvent, null); + + expect(sub1Called).toBe(2); + }); + + it('should handle errors', () => { + const bus = new EventBusSrv(); + let sub1Called = 0; + let sub2Called = 0; + + bus.on(legacyEvent, () => { + sub1Called++; + throw { message: 'hello' }; + }); + + bus.on(legacyEvent, () => { + sub2Called++; + }); + + try { + bus.emit(legacyEvent, null); + } catch (_) {} + try { + bus.emit(legacyEvent, null); + } catch (_) {} + + expect(sub1Called).toBe(2); + expect(sub2Called).toBe(0); + }); + + it('removeAllListeners should unsubscribe to all', () => { + const bus = new EventBusSrv(); + const events: LoginEvent[] = []; + + bus.subscribe(LoginEvent, event => { + events.push(event); + }); + + bus.removeAllListeners(); + bus.publish(new LoginEvent({ logins: 10 })); + + expect(events.length).toBe(0); + }); + }); +}); diff --git a/packages/grafana-data/src/events/EventBus.ts b/packages/grafana-data/src/events/EventBus.ts new file mode 100644 index 00000000000..a279788d035 --- /dev/null +++ b/packages/grafana-data/src/events/EventBus.ts @@ -0,0 +1,85 @@ +import EventEmitter from 'eventemitter3'; +import { Unsubscribable, Observable } from 'rxjs'; +import { AppEvent } from './types'; +import { EventBus, LegacyEmitter, BusEventHandler, BusEventType, LegacyEventHandler, BusEvent } from './types'; + +/** + * @alpha + */ +export class EventBusSrv implements EventBus, LegacyEmitter { + private emitter: EventEmitter; + + constructor() { + this.emitter = new EventEmitter(); + } + + publish(event: T): void { + this.emitter.emit(event.type, event); + } + + subscribe(typeFilter: BusEventType, handler: BusEventHandler): Unsubscribable { + return this.getStream(typeFilter).subscribe({ next: handler }); + } + + getStream(eventType: BusEventType): Observable { + return new Observable(observer => { + const handler = (event: T) => { + observer.next(event); + }; + + this.emitter.on(eventType.type, handler); + + return () => { + this.emitter.off(eventType.type, handler); + }; + }); + } + + /** + * Legacy functions + */ + emit(event: AppEvent | string, payload?: T | any): void { + // console.log(`Deprecated emitter function used (emit), use $emit`); + + if (typeof event === 'string') { + this.emitter.emit(event, { type: event, payload }); + } else { + this.emitter.emit(event.name, { type: event.name, payload }); + } + } + + on(event: AppEvent | string, handler: LegacyEventHandler, scope?: any) { + // console.log(`Deprecated emitter function used (on), use $on`); + + // need this wrapper to make old events compatible with old handlers + handler.wrapper = (emittedEvent: BusEvent) => { + handler(emittedEvent.payload); + }; + + if (typeof event === 'string') { + this.emitter.on(event, handler.wrapper); + } else { + this.emitter.on(event.name, handler.wrapper); + } + + if (scope) { + const unbind = scope.$on('$destroy', () => { + this.off(event, handler); + unbind(); + }); + } + } + + off(event: AppEvent | string, handler: LegacyEventHandler) { + if (typeof event === 'string') { + this.emitter.off(event, handler.wrapper); + return; + } + + this.emitter.off(event.name, handler.wrapper); + } + + removeAllListeners() { + this.emitter.removeAllListeners(); + } +} diff --git a/packages/grafana-data/src/types/utils.ts b/packages/grafana-data/src/events/eventFactory.ts similarity index 65% rename from packages/grafana-data/src/types/utils.ts rename to packages/grafana-data/src/events/eventFactory.ts index 355c502fc55..2dd1b92091f 100644 --- a/packages/grafana-data/src/types/utils.ts +++ b/packages/grafana-data/src/events/eventFactory.ts @@ -1,9 +1,7 @@ -import { AppEvent } from './appEvents'; - -export type Omit = Pick>; -export type Subtract = Omit; +import { AppEvent } from './types'; const typeList: Set = new Set(); + export function eventFactory(name: string): AppEvent { if (typeList.has(name)) { throw new Error(`There is already an event defined with type '${name}'`); diff --git a/packages/grafana-data/src/events/index.ts b/packages/grafana-data/src/events/index.ts new file mode 100644 index 00000000000..69c33f4300d --- /dev/null +++ b/packages/grafana-data/src/events/index.ts @@ -0,0 +1,3 @@ +export * from './eventFactory'; +export * from './types'; +export * from './EventBus'; diff --git a/packages/grafana-data/src/events/types.ts b/packages/grafana-data/src/events/types.ts new file mode 100644 index 00000000000..63b4738623a --- /dev/null +++ b/packages/grafana-data/src/events/types.ts @@ -0,0 +1,115 @@ +import { Unsubscribable, Observable } from 'rxjs'; + +/** + * @alpha + * internal interface + */ +export interface BusEvent { + readonly type: string; + readonly payload?: any; +} + +/** + * @alpha + * Base event type + */ +export abstract class BusEventBase implements BusEvent { + readonly type: string; + readonly payload?: any; + + constructor() { + //@ts-ignore + this.type = this.__proto__.constructor.type; + } +} + +/** + * @alpha + * Base event type with payload + */ +export abstract class BusEventWithPayload extends BusEventBase { + readonly payload: T; + + constructor(payload: T) { + super(); + this.payload = payload; + } +} + +/* + * Interface for an event type constructor + */ +export interface BusEventType { + type: string; + new (...args: any[]): T; +} + +/** + * @alpha + * Event callback/handler type + */ +export interface BusEventHandler { + (event: T): void; +} + +/** + * @alpha + * Main minimal interface + */ +export interface EventBus { + /** + * Publish single vent + */ + publish(event: T): void; + + /** + * Subscribe to single event + */ + subscribe(eventType: BusEventType, handler: BusEventHandler): Unsubscribable; + + /** + * Get observable of events + */ + getStream(eventType: BusEventType): Observable; + + /** + * Remove all event subscriptions + */ + removeAllListeners(): void; +} + +/** + * @public + * @deprecated event type + */ +export interface AppEvent { + readonly name: string; + payload?: T; +} + +/** @public */ +export interface LegacyEmitter { + /** + * @deprecated use $emit + */ + emit(event: AppEvent | string, payload?: T): void; + + /** + * @deprecated use $on + */ + on(event: AppEvent | string, handler: LegacyEventHandler, scope?: any): void; + + /** + * @deprecated use $on + */ + off(event: AppEvent | string, handler: (payload?: T | any) => void): void; +} + +/** @public */ +export interface LegacyEventHandler { + (payload: T): void; + wrapper?: (event: BusEvent) => void; +} + +/** @alpha */ +export interface EventBusExtended extends EventBus, LegacyEmitter {} diff --git a/packages/grafana-data/src/index.ts b/packages/grafana-data/src/index.ts index 43a488d7095..741f9584f22 100644 --- a/packages/grafana-data/src/index.ts +++ b/packages/grafana-data/src/index.ts @@ -12,4 +12,5 @@ export * from './datetime'; export * from './text'; export * from './valueFormats'; export * from './field'; +export * from './events'; export { PanelPlugin } from './panel/PanelPlugin'; diff --git a/packages/grafana-data/src/types/appEvents.ts b/packages/grafana-data/src/types/appEvents.ts deleted file mode 100644 index 5cba61f42e7..00000000000 --- a/packages/grafana-data/src/types/appEvents.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { eventFactory } from './utils'; - -export interface AppEvent { - readonly name: string; - payload?: T; -} - -export type AlertPayload = [string, string?]; -export type AlertErrorPayload = [string, (string | Error)?]; - -export const alertSuccess = eventFactory('alert-success'); -export const alertWarning = eventFactory('alert-warning'); -export const alertError = eventFactory('alert-error'); diff --git a/packages/grafana-data/src/types/index.ts b/packages/grafana-data/src/types/index.ts index 75699c2d485..7a1ac746ed2 100644 --- a/packages/grafana-data/src/types/index.ts +++ b/packages/grafana-data/src/types/index.ts @@ -7,7 +7,6 @@ export * from './navModel'; export * from './select'; export * from './time'; export * from './thresholds'; -export * from './utils'; export * from './valueMapping'; export * from './displayValue'; export * from './graph'; @@ -27,12 +26,7 @@ export * from './orgs'; export * from './flot'; export * from './trace'; export * from './explore'; +export * from './legacyEvents'; export * from './live'; -import * as AppEvents from './appEvents'; -import { AppEvent } from './appEvents'; -export { AppEvent, AppEvents }; - -import * as PanelEvents from './panelEvents'; -export { PanelEvents }; export { GrafanaConfig, BuildInfo, FeatureToggles, LicenseInfo } from './config'; diff --git a/packages/grafana-data/src/types/legacyEvents.ts b/packages/grafana-data/src/types/legacyEvents.ts new file mode 100644 index 00000000000..9a2d97f35ca --- /dev/null +++ b/packages/grafana-data/src/types/legacyEvents.ts @@ -0,0 +1,47 @@ +import { DataQueryError, DataQueryResponseData } from './datasource'; +import { AngularPanelMenuItem } from './panel'; +import { DataFrame } from './dataFrame'; +import { eventFactory } from '../events/eventFactory'; +import { BusEventBase, BusEventWithPayload } from '../events/types'; + +export type AlertPayload = [string, string?]; +export type AlertErrorPayload = [string, (string | Error)?]; + +export const AppEvents = { + alertSuccess: eventFactory('alert-success'), + alertWarning: eventFactory('alert-warning'), + alertError: eventFactory('alert-error'), +}; + +export const PanelEvents = { + refresh: eventFactory('refresh'), + componentDidMount: eventFactory('component-did-mount'), + dataReceived: eventFactory('data-received'), + dataError: eventFactory('data-error'), + dataFramesReceived: eventFactory('data-frames-received'), + dataSnapshotLoad: eventFactory('data-snapshot-load'), + editModeInitialized: eventFactory('init-edit-mode'), + initPanelActions: eventFactory('init-panel-actions'), + panelInitialized: eventFactory('panel-initialized'), + panelSizeChanged: eventFactory('panel-size-changed'), + panelTeardown: eventFactory('panel-teardown'), + render: eventFactory('render'), +}; + +/** @public */ +export interface LegacyGraphHoverEventPayload { + pos: any; + panel: { + id: number; + }; +} + +/** @alpha */ +export class LegacyGraphHoverEvent extends BusEventWithPayload { + static type = 'graph-hover'; +} + +/** @alpha */ +export class LegacyGraphHoverClearEvent extends BusEventBase { + static type = 'graph-hover-clear'; +} diff --git a/packages/grafana-data/src/types/panel.ts b/packages/grafana-data/src/types/panel.ts index df0e3ea045b..a666b841641 100644 --- a/packages/grafana-data/src/types/panel.ts +++ b/packages/grafana-data/src/types/panel.ts @@ -4,6 +4,7 @@ import { ScopedVars } from './ScopedVars'; import { LoadingState } from './data'; import { DataFrame } from './dataFrame'; import { AbsoluteTimeRange, TimeRange, TimeZone } from './time'; +import { EventBus } from '../events'; import { FieldConfigSource } from './fieldOverrides'; import { Registry } from '../utils'; import { StandardEditorProps } from '../field'; @@ -77,6 +78,9 @@ export interface PanelProps { /** Panel title */ title: string; + /** EventBus */ + eventBus: EventBus; + /** Panel options change handler */ onOptionsChange: (options: T) => void; diff --git a/packages/grafana-data/src/types/panelEvents.ts b/packages/grafana-data/src/types/panelEvents.ts deleted file mode 100644 index 1c17c49f595..00000000000 --- a/packages/grafana-data/src/types/panelEvents.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { eventFactory } from './utils'; -import { DataQueryError, DataQueryResponseData } from './datasource'; -import { AngularPanelMenuItem } from './panel'; -import { DataFrame } from './dataFrame'; - -/** Payloads */ -export interface PanelChangeViewPayload { - fullscreen?: boolean; - edit?: boolean; - panelId?: number; - toggle?: boolean; -} - -/** Events */ -export const refresh = eventFactory('refresh'); -export const componentDidMount = eventFactory('component-did-mount'); -export const dataError = eventFactory('data-error'); -export const dataReceived = eventFactory('data-received'); -export const dataFramesReceived = eventFactory('data-frames-received'); -export const dataSnapshotLoad = eventFactory('data-snapshot-load'); -export const editModeInitialized = eventFactory('init-edit-mode'); -export const initPanelActions = eventFactory('init-panel-actions'); -export const panelChangeView = eventFactory('panel-change-view'); -export const panelInitialized = eventFactory('panel-initialized'); -export const panelSizeChanged = eventFactory('panel-size-changed'); -export const panelTeardown = eventFactory('panel-teardown'); -export const render = eventFactory('render'); -export const viewModeChanged = eventFactory('view-mode-changed'); diff --git a/public/app/core/app_events.ts b/public/app/core/app_events.ts index 1951fd87001..2c3552ec35b 100644 --- a/public/app/core/app_events.ts +++ b/public/app/core/app_events.ts @@ -1,5 +1,5 @@ -import { Emitter } from './utils/emitter'; +import { EventBusSrv, EventBusExtended } from '@grafana/data'; -export const appEvents = new Emitter(); +export const appEvents: EventBusExtended = new EventBusSrv(); export default appEvents; diff --git a/public/app/core/core.ts b/public/app/core/core.ts index d7e03fb550c..3d8fb8e756f 100644 --- a/public/app/core/core.ts +++ b/public/app/core/core.ts @@ -19,7 +19,6 @@ import { colors, JsonExplorer } from '@grafana/ui/'; import { infoPopover } from './components/info_popover'; import { arrayJoin } from './directives/array_join'; -import { Emitter } from './utils/emitter'; import { switchDirective } from './components/switch'; import { dashboardSelector } from './components/dashboard_selector'; import { queryPartEditorDirective } from './components/query_part/query_part_editor'; @@ -47,7 +46,6 @@ export { coreModule, switchDirective, infoPopover, - Emitter, appEvents, dashboardSelector, queryPartEditorDirective, diff --git a/public/app/core/services/backend_srv.ts b/public/app/core/services/backend_srv.ts index ce2d9d1e17a..62060b2fa4d 100644 --- a/public/app/core/services/backend_srv.ts +++ b/public/app/core/services/backend_srv.ts @@ -11,7 +11,6 @@ import { DashboardSearchHit } from 'app/features/search/types'; import { FolderDTO } from 'app/types'; import { coreModule } from 'app/core/core_module'; import { ContextSrv, contextSrv } from './context_srv'; -import { Emitter } from '../utils/emitter'; import { parseInitFromOptions, parseUrlFromOptions } from '../utils/fetch'; import { isDataQuery, isLocalUrl } from '../utils/query'; import { FetchQueue } from './FetchQueue'; @@ -22,7 +21,7 @@ const CANCEL_ALL_REQUESTS_REQUEST_ID = 'cancel_all_requests_request_id'; export interface BackendSrvDependencies { fromFetch: (input: string | Request, init?: RequestInit) => Observable; - appEvents: Emitter; + appEvents: typeof appEvents; contextSrv: ContextSrv; logout: () => void; } diff --git a/public/app/core/services/keybindingSrv.ts b/public/app/core/services/keybindingSrv.ts index 0ec85fc66ab..58fd242110f 100644 --- a/public/app/core/services/keybindingSrv.ts +++ b/public/app/core/services/keybindingSrv.ts @@ -2,7 +2,7 @@ import _ from 'lodash'; import Mousetrap from 'mousetrap'; import 'mousetrap-global-bind'; import { ILocationService, IRootScopeService, ITimeoutService } from 'angular'; -import { locationUtil } from '@grafana/data'; +import { LegacyGraphHoverClearEvent, locationUtil } from '@grafana/data'; import coreModule from 'app/core/core_module'; import appEvents from 'app/core/app_events'; @@ -197,7 +197,7 @@ export class KeybindingSrv { setupDashboardBindings(scope: IRootScopeService & AppEventEmitter, dashboard: DashboardModel) { this.bind('mod+o', () => { dashboard.graphTooltip = (dashboard.graphTooltip + 1) % 3; - appEvents.emit(CoreEvents.graphHoverClear); + dashboard.events.publish(new LegacyGraphHoverClearEvent()); dashboard.startRefresh(); }); diff --git a/public/app/core/specs/backend_srv.test.ts b/public/app/core/specs/backend_srv.test.ts index 2d26e0d1a00..8096ca578d8 100644 --- a/public/app/core/specs/backend_srv.test.ts +++ b/public/app/core/specs/backend_srv.test.ts @@ -1,10 +1,9 @@ import 'whatwg-fetch'; // fetch polyfill needed for PhantomJs rendering import { Observable, of } from 'rxjs'; import { delay } from 'rxjs/operators'; -import { AppEvents, DataQueryErrorType } from '@grafana/data'; +import { AppEvents, DataQueryErrorType, EventBusExtended } from '@grafana/data'; import { BackendSrv } from '../services/backend_srv'; -import { Emitter } from '../utils/emitter'; import { ContextSrv, User } from '../services/context_srv'; import { describe, expect } from '../../../test/lib/common'; import { BackendSrvRequest, FetchError } from '@grafana/runtime'; @@ -35,9 +34,11 @@ const getTestContext = (overides?: object) => { }; return of(mockedResponse); }); - const appEventsMock: Emitter = ({ + + const appEventsMock: EventBusExtended = ({ emit: jest.fn(), - } as any) as Emitter; + } as any) as EventBusExtended; + const user: User = ({ isSignedIn: props.isSignedIn, orgId: props.orgId, diff --git a/public/app/core/specs/emitter.test.ts b/public/app/core/specs/emitter.test.ts deleted file mode 100644 index 8d19d5d8724..00000000000 --- a/public/app/core/specs/emitter.test.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { Emitter } from '../utils/emitter'; -import { eventFactory } from '@grafana/data'; - -const testEvent = eventFactory('test'); - -describe('Emitter', () => { - describe('given 2 subscribers', () => { - it('should notfiy subscribers', () => { - const events = new Emitter(); - let sub1Called = false; - let sub2Called = false; - - events.on(testEvent, () => { - sub1Called = true; - }); - events.on(testEvent, () => { - sub2Called = true; - }); - - events.emit(testEvent, null); - - expect(sub1Called).toBe(true); - expect(sub2Called).toBe(true); - }); - - it('when subscribing twice', () => { - const events = new Emitter(); - let sub1Called = 0; - - function handler() { - sub1Called += 1; - } - - events.on(testEvent, handler); - events.on(testEvent, handler); - - events.emit(testEvent, null); - - expect(sub1Called).toBe(2); - }); - - it('should handle errors', () => { - const events = new Emitter(); - let sub1Called = 0; - let sub2Called = 0; - - events.on(testEvent, () => { - sub1Called++; - throw { message: 'hello' }; - }); - - events.on(testEvent, () => { - sub2Called++; - }); - - try { - events.emit(testEvent, null); - } catch (_) {} - try { - events.emit(testEvent, null); - } catch (_) {} - - expect(sub1Called).toBe(2); - expect(sub2Called).toBe(0); - }); - }); -}); diff --git a/public/app/core/utils/emitter.ts b/public/app/core/utils/emitter.ts deleted file mode 100644 index 4c449f81d59..00000000000 --- a/public/app/core/utils/emitter.ts +++ /dev/null @@ -1,101 +0,0 @@ -import EventEmitter3, { EventEmitter } from 'eventemitter3'; -import { AppEvent } from '@grafana/data'; - -export class Emitter { - private emitter: EventEmitter3; - - constructor() { - this.emitter = new EventEmitter(); - } - - /** - * DEPRECATED. - */ - emit(name: string, data?: any): void; - - /** - * Emits an `event` with `payload`. - */ - emit(event: AppEvent): void; - emit : unknown) extends T ? Partial : never, U = any>( - event: AppEvent - ): void; - emit(event: AppEvent, payload: T): void; - emit(event: AppEvent | string, payload?: T | any): void { - if (typeof event === 'string') { - console.warn(`Using strings as events is deprecated and will be removed in a future version. (${event})`); - this.emitter.emit(event, payload); - } else { - this.emitter.emit(event.name, payload); - } - } - - /** - * DEPRECATED. - */ - on(name: string, handler: (payload?: any) => void, scope?: any): void; - - /** - * Handles `event` with `handler()` when emitted. - */ - on(event: AppEvent, handler: () => void, scope?: any): void; - on : unknown) extends T ? Partial : never, U = any>( - event: AppEvent, - handler: () => void, - scope?: any - ): void; - on(event: AppEvent, handler: (payload: T) => void, scope?: any): void; - on(event: AppEvent | string, handler: (payload?: T | any) => void, scope?: any) { - if (typeof event === 'string') { - console.warn(`Using strings as events is deprecated and will be removed in a future version. (${event})`); - this.emitter.on(event, handler); - - if (scope) { - const unbind = scope.$on('$destroy', () => { - this.emitter.off(event, handler); - unbind(); - }); - } - return; - } - - this.emitter.on(event.name, handler); - - if (scope) { - const unbind = scope.$on('$destroy', () => { - this.emitter.off(event.name, handler); - unbind(); - }); - } - } - - /** - * DEPRECATED. - */ - off(name: string, handler: (payload?: any) => void): void; - - off(event: AppEvent, handler: () => void): void; - off : unknown) extends T ? Partial : never, U = any>( - event: AppEvent, - handler: () => void, - scope?: any - ): void; - off(event: AppEvent, handler: (payload: T) => void): void; - off(event: AppEvent | string, handler: (payload?: T | any) => void) { - if (typeof event === 'string') { - console.warn(`Using strings as events is deprecated and will be removed in a future version. (${event})`); - this.emitter.off(event, handler); - return; - } - - this.emitter.off(event.name, handler); - } - - removeAllListeners(evt?: string) { - this.emitter.removeAllListeners(evt); - } - - getEventCount(): number { - return (this.emitter as any)._eventsCount; - } -} diff --git a/public/app/features/dashboard/components/DashboardSettings/SettingsCtrl.ts b/public/app/features/dashboard/components/DashboardSettings/SettingsCtrl.ts index 54af043cf38..594bada9245 100644 --- a/public/app/features/dashboard/components/DashboardSettings/SettingsCtrl.ts +++ b/public/app/features/dashboard/components/DashboardSettings/SettingsCtrl.ts @@ -39,8 +39,8 @@ export class SettingsCtrl { this.$scope.$on('$destroy', () => { this.dashboard.updateSubmenuVisibility(); + setTimeout(() => { - this.$rootScope.appEvent(CoreEvents.dashScroll, { restore: true }); this.dashboard.startRefresh(); }); }); @@ -53,7 +53,6 @@ export class SettingsCtrl { this.onRouteUpdated(); this.$rootScope.onAppEvent(CoreEvents.routeUpdated, this.onRouteUpdated.bind(this), $scope); - this.$rootScope.appEvent(CoreEvents.dashScroll, { animate: false, pos: 0 }); appEvents.on(CoreEvents.dashboardSaved, this.onPostSave.bind(this), $scope); diff --git a/public/app/features/dashboard/components/VersionHistory/HistoryListCtrl.ts b/public/app/features/dashboard/components/VersionHistory/HistoryListCtrl.ts index 8d86412a496..e9b461f4edd 100644 --- a/public/app/features/dashboard/components/VersionHistory/HistoryListCtrl.ts +++ b/public/app/features/dashboard/components/VersionHistory/HistoryListCtrl.ts @@ -57,9 +57,7 @@ export class HistoryListCtrl { } } - dismiss() { - this.$rootScope.appEvent(CoreEvents.hideDashEditor); - } + dismiss() {} addToLog() { this.start = this.start + this.limit; diff --git a/public/app/features/dashboard/containers/__snapshots__/DashboardPage.test.tsx.snap b/public/app/features/dashboard/containers/__snapshots__/DashboardPage.test.tsx.snap index 37c2165a06a..46207e3a72a 100644 --- a/public/app/features/dashboard/containers/__snapshots__/DashboardPage.test.tsx.snap +++ b/public/app/features/dashboard/containers/__snapshots__/DashboardPage.test.tsx.snap @@ -24,7 +24,7 @@ exports[`DashboardPage Dashboard init completed Should render dashboard grid 1` "autoUpdate": undefined, "description": undefined, "editable": true, - "events": Emitter { + "events": EventBusSrv { "emitter": EventEmitter { "_events": Object {}, "_eventsCount": 0, @@ -53,7 +53,7 @@ exports[`DashboardPage Dashboard init completed Should render dashboard grid 1` PanelModel { "cachedPluginOptions": Object {}, "datasource": null, - "events": Emitter { + "events": EventBusSrv { "emitter": EventEmitter { "_events": Object {}, "_eventsCount": 0, @@ -138,7 +138,7 @@ exports[`DashboardPage Dashboard init completed Should render dashboard grid 1` "autoUpdate": undefined, "description": undefined, "editable": true, - "events": Emitter { + "events": EventBusSrv { "emitter": EventEmitter { "_events": Object {}, "_eventsCount": 0, @@ -167,7 +167,7 @@ exports[`DashboardPage Dashboard init completed Should render dashboard grid 1` PanelModel { "cachedPluginOptions": Object {}, "datasource": null, - "events": Emitter { + "events": EventBusSrv { "emitter": EventEmitter { "_events": Object {}, "_eventsCount": 0, @@ -233,7 +233,7 @@ exports[`DashboardPage Dashboard init completed Should render dashboard grid 1` "autoUpdate": undefined, "description": undefined, "editable": true, - "events": Emitter { + "events": EventBusSrv { "emitter": EventEmitter { "_events": Object {}, "_eventsCount": 0, @@ -262,7 +262,7 @@ exports[`DashboardPage Dashboard init completed Should render dashboard grid 1` PanelModel { "cachedPluginOptions": Object {}, "datasource": null, - "events": Emitter { + "events": EventBusSrv { "emitter": EventEmitter { "_events": Object {}, "_eventsCount": 0, @@ -384,7 +384,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti "autoUpdate": undefined, "description": undefined, "editable": true, - "events": Emitter { + "events": EventBusSrv { "emitter": EventEmitter { "_events": Object {}, "_eventsCount": 0, @@ -413,7 +413,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti PanelModel { "cachedPluginOptions": Object {}, "datasource": null, - "events": Emitter { + "events": EventBusSrv { "emitter": EventEmitter { "_events": Object {}, "_eventsCount": 0, @@ -498,7 +498,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti "autoUpdate": undefined, "description": undefined, "editable": true, - "events": Emitter { + "events": EventBusSrv { "emitter": EventEmitter { "_events": Object {}, "_eventsCount": 0, @@ -527,7 +527,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti PanelModel { "cachedPluginOptions": Object {}, "datasource": null, - "events": Emitter { + "events": EventBusSrv { "emitter": EventEmitter { "_events": Object {}, "_eventsCount": 0, @@ -593,7 +593,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti "autoUpdate": undefined, "description": undefined, "editable": true, - "events": Emitter { + "events": EventBusSrv { "emitter": EventEmitter { "_events": Object {}, "_eventsCount": 0, @@ -622,7 +622,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti PanelModel { "cachedPluginOptions": Object {}, "datasource": null, - "events": Emitter { + "events": EventBusSrv { "emitter": EventEmitter { "_events": Object {}, "_eventsCount": 0, @@ -693,7 +693,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti "autoUpdate": undefined, "description": undefined, "editable": true, - "events": Emitter { + "events": EventBusSrv { "emitter": EventEmitter { "_events": Object {}, "_eventsCount": 0, @@ -722,7 +722,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti PanelModel { "cachedPluginOptions": Object {}, "datasource": null, - "events": Emitter { + "events": EventBusSrv { "emitter": EventEmitter { "_events": Object {}, "_eventsCount": 0, diff --git a/public/app/features/dashboard/dashgrid/PanelChrome.tsx b/public/app/features/dashboard/dashgrid/PanelChrome.tsx index 759618af56b..171d307f914 100644 --- a/public/app/features/dashboard/dashgrid/PanelChrome.tsx +++ b/public/app/features/dashboard/dashgrid/PanelChrome.tsx @@ -52,8 +52,9 @@ export interface State { } export class PanelChrome extends PureComponent { - timeSrv: TimeSrv = getTimeSrv(); - querySubscription: Unsubscribable; + readonly timeSrv: TimeSrv = getTimeSrv(); + + querySubscription?: Unsubscribable; constructor(props: Props) { super(props); @@ -73,6 +74,7 @@ export class PanelChrome extends PureComponent { componentDidMount() { const { panel, dashboard } = this.props; + // Subscribe to panel events panel.events.on(PanelEvents.refresh, this.onRefresh); panel.events.on(PanelEvents.render, this.onRender); @@ -244,7 +246,7 @@ export class PanelChrome extends PureComponent { } renderPanel(width: number, height: number) { - const { panel, plugin } = this.props; + const { panel, plugin, dashboard } = this.props; const { renderCounter, data, isFirstLoad } = this.state; const { theme } = config; const { state: loadingState } = data; @@ -291,6 +293,7 @@ export class PanelChrome extends PureComponent { onOptionsChange={this.onOptionsChange} onFieldConfigChange={this.onFieldConfigChange} onChangeTimeRange={this.onChangeTimeRange} + eventBus={dashboard.events} /> diff --git a/public/app/features/dashboard/dashgrid/__snapshots__/DashboardGrid.test.tsx.snap b/public/app/features/dashboard/dashgrid/__snapshots__/DashboardGrid.test.tsx.snap index 016ca4b23f0..0cc9e30177f 100644 --- a/public/app/features/dashboard/dashgrid/__snapshots__/DashboardGrid.test.tsx.snap +++ b/public/app/features/dashboard/dashgrid/__snapshots__/DashboardGrid.test.tsx.snap @@ -68,7 +68,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = ` "autoUpdate": undefined, "description": undefined, "editable": true, - "events": Emitter { + "events": EventBusSrv { "emitter": EventEmitter { "_events": Object { "panel-added": EE { @@ -123,7 +123,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = ` PanelModel { "cachedPluginOptions": Object {}, "datasource": null, - "events": Emitter { + "events": EventBusSrv { "emitter": EventEmitter { "_events": Object {}, "_eventsCount": 0, @@ -151,7 +151,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = ` PanelModel { "cachedPluginOptions": Object {}, "datasource": null, - "events": Emitter { + "events": EventBusSrv { "emitter": EventEmitter { "_events": Object {}, "_eventsCount": 0, @@ -179,7 +179,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = ` PanelModel { "cachedPluginOptions": Object {}, "datasource": null, - "events": Emitter { + "events": EventBusSrv { "emitter": EventEmitter { "_events": Object {}, "_eventsCount": 0, @@ -207,7 +207,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = ` PanelModel { "cachedPluginOptions": Object {}, "datasource": null, - "events": Emitter { + "events": EventBusSrv { "emitter": EventEmitter { "_events": Object {}, "_eventsCount": 0, @@ -258,7 +258,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = ` PanelModel { "cachedPluginOptions": Object {}, "datasource": null, - "events": Emitter { + "events": EventBusSrv { "emitter": EventEmitter { "_events": Object {}, "_eventsCount": 0, @@ -310,7 +310,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = ` "autoUpdate": undefined, "description": undefined, "editable": true, - "events": Emitter { + "events": EventBusSrv { "emitter": EventEmitter { "_events": Object { "panel-added": EE { @@ -365,7 +365,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = ` PanelModel { "cachedPluginOptions": Object {}, "datasource": null, - "events": Emitter { + "events": EventBusSrv { "emitter": EventEmitter { "_events": Object {}, "_eventsCount": 0, @@ -393,7 +393,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = ` PanelModel { "cachedPluginOptions": Object {}, "datasource": null, - "events": Emitter { + "events": EventBusSrv { "emitter": EventEmitter { "_events": Object {}, "_eventsCount": 0, @@ -421,7 +421,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = ` PanelModel { "cachedPluginOptions": Object {}, "datasource": null, - "events": Emitter { + "events": EventBusSrv { "emitter": EventEmitter { "_events": Object {}, "_eventsCount": 0, @@ -449,7 +449,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = ` PanelModel { "cachedPluginOptions": Object {}, "datasource": null, - "events": Emitter { + "events": EventBusSrv { "emitter": EventEmitter { "_events": Object {}, "_eventsCount": 0, @@ -500,7 +500,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = ` PanelModel { "cachedPluginOptions": Object {}, "datasource": null, - "events": Emitter { + "events": EventBusSrv { "emitter": EventEmitter { "_events": Object {}, "_eventsCount": 0, @@ -552,7 +552,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = ` "autoUpdate": undefined, "description": undefined, "editable": true, - "events": Emitter { + "events": EventBusSrv { "emitter": EventEmitter { "_events": Object { "panel-added": EE { @@ -607,7 +607,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = ` PanelModel { "cachedPluginOptions": Object {}, "datasource": null, - "events": Emitter { + "events": EventBusSrv { "emitter": EventEmitter { "_events": Object {}, "_eventsCount": 0, @@ -635,7 +635,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = ` PanelModel { "cachedPluginOptions": Object {}, "datasource": null, - "events": Emitter { + "events": EventBusSrv { "emitter": EventEmitter { "_events": Object {}, "_eventsCount": 0, @@ -663,7 +663,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = ` PanelModel { "cachedPluginOptions": Object {}, "datasource": null, - "events": Emitter { + "events": EventBusSrv { "emitter": EventEmitter { "_events": Object {}, "_eventsCount": 0, @@ -691,7 +691,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = ` PanelModel { "cachedPluginOptions": Object {}, "datasource": null, - "events": Emitter { + "events": EventBusSrv { "emitter": EventEmitter { "_events": Object {}, "_eventsCount": 0, @@ -742,7 +742,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = ` PanelModel { "cachedPluginOptions": Object {}, "datasource": null, - "events": Emitter { + "events": EventBusSrv { "emitter": EventEmitter { "_events": Object {}, "_eventsCount": 0, @@ -794,7 +794,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = ` "autoUpdate": undefined, "description": undefined, "editable": true, - "events": Emitter { + "events": EventBusSrv { "emitter": EventEmitter { "_events": Object { "panel-added": EE { @@ -849,7 +849,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = ` PanelModel { "cachedPluginOptions": Object {}, "datasource": null, - "events": Emitter { + "events": EventBusSrv { "emitter": EventEmitter { "_events": Object {}, "_eventsCount": 0, @@ -877,7 +877,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = ` PanelModel { "cachedPluginOptions": Object {}, "datasource": null, - "events": Emitter { + "events": EventBusSrv { "emitter": EventEmitter { "_events": Object {}, "_eventsCount": 0, @@ -905,7 +905,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = ` PanelModel { "cachedPluginOptions": Object {}, "datasource": null, - "events": Emitter { + "events": EventBusSrv { "emitter": EventEmitter { "_events": Object {}, "_eventsCount": 0, @@ -933,7 +933,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = ` PanelModel { "cachedPluginOptions": Object {}, "datasource": null, - "events": Emitter { + "events": EventBusSrv { "emitter": EventEmitter { "_events": Object {}, "_eventsCount": 0, @@ -984,7 +984,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = ` PanelModel { "cachedPluginOptions": Object {}, "datasource": null, - "events": Emitter { + "events": EventBusSrv { "emitter": EventEmitter { "_events": Object {}, "_eventsCount": 0, diff --git a/public/app/features/dashboard/panel_editor/QueryEditorRow.tsx b/public/app/features/dashboard/panel_editor/QueryEditorRow.tsx index b111aeb6fde..626cdb45eda 100644 --- a/public/app/features/dashboard/panel_editor/QueryEditorRow.tsx +++ b/public/app/features/dashboard/panel_editor/QueryEditorRow.tsx @@ -5,7 +5,6 @@ import _ from 'lodash'; // Utils & Services import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { AngularComponent, getAngularLoader } from '@grafana/runtime'; -import { Emitter } from 'app/core/utils/emitter'; import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv'; // Types import { PanelModel } from '../state/PanelModel'; @@ -19,6 +18,7 @@ import { PanelEvents, TimeRange, toLegacyResponseData, + EventBusExtended, } from '@grafana/data'; import { QueryEditorRowTitle } from './QueryEditorRowTitle'; import { QueryOperationRow } from 'app/core/components/QueryOperationRow/QueryOperationRow'; @@ -331,7 +331,7 @@ export interface AngularQueryComponentScope { target: DataQuery; panel: PanelModel; dashboard: DashboardModel; - events: Emitter; + events: EventBusExtended; refresh: () => void; render: () => void; datasource: DataSourceApi | null; diff --git a/public/app/features/dashboard/state/DashboardModel.ts b/public/app/features/dashboard/state/DashboardModel.ts index 172f02f46b9..070c162ea91 100644 --- a/public/app/features/dashboard/state/DashboardModel.ts +++ b/public/app/features/dashboard/state/DashboardModel.ts @@ -4,7 +4,6 @@ import _ from 'lodash'; import { DEFAULT_ANNOTATION_COLOR } from '@grafana/ui'; import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN, GRID_COLUMN_COUNT, REPEAT_DIR_VERTICAL } from 'app/core/constants'; // Utils & Services -import { Emitter } from 'app/core/utils/emitter'; import { contextSrv } from 'app/core/services/context_srv'; import sortByKeys from 'app/core/utils/sort_by_keys'; // Types @@ -19,6 +18,8 @@ import { TimeRange, TimeZone, UrlQueryValue, + EventBusSrv, + EventBusExtended, } from '@grafana/data'; import { CoreEvents, DashboardMeta, KIOSK_MODE_TV } from 'app/types'; import { GetVariables, getVariables } from 'app/features/variables/state/selectors'; @@ -82,7 +83,7 @@ export class DashboardModel { // repeat process cycles iteration?: number; meta: DashboardMeta; - events: Emitter; + events: EventBusExtended; static nonPersistedProperties: { [str: string]: boolean } = { events: true, @@ -101,7 +102,7 @@ export class DashboardModel { data = {}; } - this.events = new Emitter(); + this.events = new EventBusSrv(); this.id = data.id || null; this.uid = data.uid || null; this.revision = data.revision; diff --git a/public/app/features/dashboard/state/PanelModel.ts b/public/app/features/dashboard/state/PanelModel.ts index c23451c2884..b4e695daf21 100644 --- a/public/app/features/dashboard/state/PanelModel.ts +++ b/public/app/features/dashboard/state/PanelModel.ts @@ -2,7 +2,6 @@ import _ from 'lodash'; // Utils import { getTemplateSrv } from '@grafana/runtime'; -import { Emitter } from 'app/core/utils/emitter'; import { getNextRefIdChar } from 'app/core/utils/query'; // Types import { @@ -19,6 +18,8 @@ import { ScopedVars, ThresholdsConfig, ThresholdsMode, + EventBusExtended, + EventBusSrv, } from '@grafana/data'; import { EDIT_PANEL_ID } from 'app/core/constants'; import config from 'app/core/config'; @@ -145,7 +146,7 @@ export class PanelModel implements DataConfigSource { isInView: boolean; hasRefreshed: boolean; - events: Emitter; + events: EventBusExtended; cacheTimeout?: any; cachedPluginOptions?: any; legend?: { show: boolean; sort?: string; sortDesc?: boolean }; @@ -154,7 +155,7 @@ export class PanelModel implements DataConfigSource { private queryRunner?: PanelQueryRunner; constructor(model: any) { - this.events = new Emitter(); + this.events = new EventBusSrv(); this.restoreModel(model); this.replaceVariables = this.replaceVariables.bind(this); } diff --git a/public/app/features/dashboard/state/PanelQueryRunner.ts b/public/app/features/dashboard/state/PanelQueryRunner.ts index 4f1cf8818a8..cb94ce09b8e 100644 --- a/public/app/features/dashboard/state/PanelQueryRunner.ts +++ b/public/app/features/dashboard/state/PanelQueryRunner.ts @@ -197,7 +197,7 @@ export class PanelQueryRunner { } this.subscription = observable.subscribe({ - next: (data: PanelData) => { + next: data => { this.lastResult = preProcessPanelData(data, this.lastResult); // Store preprocessed query results for applying overrides later on in the pipeline this.subject.next(this.lastResult); diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index cba993e8bb6..506766b78ee 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -20,6 +20,8 @@ import { TimeZone, ExploreUrlState, LogsModel, + EventBusExtended, + EventBusSrv, } from '@grafana/data'; import store from 'app/core/store'; @@ -50,7 +52,6 @@ import { getTimeRangeFromUrl, lastUsedDatasourceKeyForOrgId, } from 'app/core/utils/explore'; -import { Emitter } from 'app/core/utils/emitter'; import { ExploreToolbar } from './ExploreToolbar'; import { NoDataSourceCallToAction } from './NoDataSourceCallToAction'; import { getTimeZone } from '../profile/state/selectors'; @@ -159,11 +160,11 @@ interface ExploreState { */ export class Explore extends React.PureComponent { el: any; - exploreEvents: Emitter; + exploreEvents: EventBusExtended; constructor(props: ExploreProps) { super(props); - this.exploreEvents = new Emitter(); + this.exploreEvents = new EventBusSrv(); this.state = { openDrawer: undefined, }; diff --git a/public/app/features/explore/QueryEditor.tsx b/public/app/features/explore/QueryEditor.tsx index 98e02f8f181..9baa27a5d7a 100644 --- a/public/app/features/explore/QueryEditor.tsx +++ b/public/app/features/explore/QueryEditor.tsx @@ -5,9 +5,7 @@ import React, { PureComponent } from 'react'; import { getAngularLoader, AngularComponent } from '@grafana/runtime'; // Types -import { Emitter } from 'app/core/utils/emitter'; -import { DataQuery } from '@grafana/data'; -import { TimeRange } from '@grafana/data'; +import { DataQuery, TimeRange, EventBusExtended } from '@grafana/data'; import 'app/features/plugins/plugin_loader'; interface QueryEditorProps { @@ -16,7 +14,7 @@ interface QueryEditorProps { onExecuteQuery?: () => void; onQueryChange?: (value: DataQuery) => void; initialQuery: DataQuery; - exploreEvents: Emitter; + exploreEvents: EventBusExtended; range: TimeRange; textEditModeEnabled?: boolean; } diff --git a/public/app/features/explore/QueryRow.test.tsx b/public/app/features/explore/QueryRow.test.tsx index f26193cbe0b..dc48d372be2 100644 --- a/public/app/features/explore/QueryRow.test.tsx +++ b/public/app/features/explore/QueryRow.test.tsx @@ -2,14 +2,14 @@ import React from 'react'; import { QueryRow, QueryRowProps } from './QueryRow'; import { shallow } from 'enzyme'; import { ExploreId } from 'app/types/explore'; -import { Emitter } from 'app/core/utils/emitter'; +import { EventBusExtended } from '@grafana/data'; import { DataSourceApi, TimeRange, AbsoluteTimeRange, PanelData } from '@grafana/data'; const setup = (propOverrides?: object) => { const props: QueryRowProps = { exploreId: ExploreId.left, index: 1, - exploreEvents: {} as Emitter, + exploreEvents: {} as EventBusExtended, changeQuery: jest.fn(), datasourceInstance: {} as DataSourceApi, highlightLogsExpressionAction: jest.fn() as any, diff --git a/public/app/features/explore/QueryRow.tsx b/public/app/features/explore/QueryRow.tsx index cde59816f5a..e27c506e953 100644 --- a/public/app/features/explore/QueryRow.tsx +++ b/public/app/features/explore/QueryRow.tsx @@ -20,18 +20,18 @@ import { TimeRange, AbsoluteTimeRange, LoadingState, + EventBusExtended, } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { ExploreItemState, ExploreId } from 'app/types/explore'; -import { Emitter } from 'app/core/utils/emitter'; import { highlightLogsExpressionAction, removeQueryRowAction } from './state/actionTypes'; import { ErrorContainer } from './ErrorContainer'; interface PropsFromParent { exploreId: ExploreId; index: number; - exploreEvents: Emitter; + exploreEvents: EventBusExtended; } export interface QueryRowProps extends PropsFromParent { diff --git a/public/app/features/explore/QueryRows.tsx b/public/app/features/explore/QueryRows.tsx index 4b5a16ef781..264131f44ef 100644 --- a/public/app/features/explore/QueryRows.tsx +++ b/public/app/features/explore/QueryRows.tsx @@ -5,12 +5,12 @@ import React, { PureComponent } from 'react'; import QueryRow from './QueryRow'; // Types -import { Emitter } from 'app/core/utils/emitter'; +import { EventBusExtended } from '@grafana/data'; import { ExploreId } from 'app/types/explore'; interface QueryRowsProps { className?: string; - exploreEvents: Emitter; + exploreEvents: EventBusExtended; exploreId: ExploreId; queryKeys: string[]; } diff --git a/public/app/features/explore/__snapshots__/Explore.test.tsx.snap b/public/app/features/explore/__snapshots__/Explore.test.tsx.snap index 90e075ab8a2..3b9641966c5 100644 --- a/public/app/features/explore/__snapshots__/Explore.test.tsx.snap +++ b/public/app/features/explore/__snapshots__/Explore.test.tsx.snap @@ -17,7 +17,7 @@ exports[`Explore should render component 1`] = ` > ({ const setup = (updateOverides?: Partial) => { const exploreId = ExploreId.left; const containerWidth = 1920; - const eventBridge = {} as Emitter; + const eventBridge = {} as EventBusExtended; const timeZone = DefaultTimeZone; const range = testRange; const urlState: ExploreUrlState = { diff --git a/public/app/features/explore/state/actions.ts b/public/app/features/explore/state/actions.ts index d794dbdba5c..87656c6a8d4 100644 --- a/public/app/features/explore/state/actions.ts +++ b/public/app/features/explore/state/actions.ts @@ -14,6 +14,7 @@ import { LoadingState, LogsDedupStrategy, PanelData, + EventBusExtended, QueryFixAction, RawTimeRange, TimeRange, @@ -21,7 +22,6 @@ import { // Services & Utils import store from 'app/core/store'; import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; -import { Emitter } from 'app/core/core'; import { buildQueryTransaction, clearQueryKeys, @@ -280,7 +280,7 @@ export function initializeExplore( queries: DataQuery[], range: TimeRange, containerWidth: number, - eventBridge: Emitter, + eventBridge: EventBusExtended, originPanelId?: number | null ): ThunkResult { return async (dispatch, getState) => { diff --git a/public/app/features/explore/state/reducers.ts b/public/app/features/explore/state/reducers.ts index d0ab8bcb911..194f2cbd7cd 100644 --- a/public/app/features/explore/state/reducers.ts +++ b/public/app/features/explore/state/reducers.ts @@ -11,6 +11,7 @@ import { PanelEvents, sortLogsResult, toLegacyResponseData, + EventBusExtended, } from '@grafana/data'; import { RefreshPicker } from '@grafana/ui'; import { LocationUpdate } from '@grafana/runtime'; @@ -62,7 +63,6 @@ import { updateDatasourceInstanceAction, } from './actionTypes'; import { updateLocation } from '../../../core/actions'; -import { Emitter } from 'app/core/core'; export const DEFAULT_RANGE = { from: 'now-6h', @@ -111,7 +111,7 @@ export const makeExploreItemState = (): ExploreItemState => ({ graphResult: null, logsResult: null, dedupStrategy: LogsDedupStrategy.none, - eventBridge: (null as unknown) as Emitter, + eventBridge: (null as unknown) as EventBusExtended, }); export const createEmptyQueryResponse = (): PanelData => ({ diff --git a/public/app/features/panel/panel_ctrl.ts b/public/app/features/panel/panel_ctrl.ts index d49f3ed56b3..97c48ca392b 100644 --- a/public/app/features/panel/panel_ctrl.ts +++ b/public/app/features/panel/panel_ctrl.ts @@ -1,9 +1,8 @@ import _ from 'lodash'; import config from 'app/core/config'; import { profiler } from 'app/core/core'; -import { Emitter } from 'app/core/utils/emitter'; import { auto } from 'angular'; -import { AppEvent, PanelEvents, PanelPluginMeta, AngularPanelMenuItem } from '@grafana/data'; +import { AppEvent, PanelEvents, PanelPluginMeta, AngularPanelMenuItem, EventBusExtended } from '@grafana/data'; import { DashboardModel } from '../dashboard/state'; export class PanelCtrl { @@ -21,7 +20,7 @@ export class PanelCtrl { height: number; width: number; containerHeight: any; - events: Emitter; + events: EventBusExtended; loading: boolean; timing: any; diff --git a/public/app/plugins/panel/bargauge/BarGaugePanel.test.tsx b/public/app/plugins/panel/bargauge/BarGaugePanel.test.tsx index 1597ecc456a..97799f1b078 100644 --- a/public/app/plugins/panel/bargauge/BarGaugePanel.test.tsx +++ b/public/app/plugins/panel/bargauge/BarGaugePanel.test.tsx @@ -97,6 +97,7 @@ function createBarGaugePanelWithData(data: PanelData): ReactWrapper ); } diff --git a/public/app/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts index 96d5287ec99..cec92e6b90c 100644 --- a/public/app/plugins/panel/graph/graph.ts +++ b/public/app/plugins/panel/graph/graph.ts @@ -11,7 +11,7 @@ import './jquery.flot.events'; import $ from 'jquery'; import _ from 'lodash'; import { tickStep } from 'app/core/utils/ticks'; -import { appEvents, coreModule, updateLegendValues } from 'app/core/core'; +import { coreModule, updateLegendValues } from 'app/core/core'; import GraphTooltip from './graph_tooltip'; import { ThresholdManager } from './threshold_manager'; import { TimeRegionManager } from './time_region_manager'; @@ -37,6 +37,8 @@ import { getTimeField, getValueFormat, hasLinks, + LegacyGraphHoverClearEvent, + LegacyGraphHoverEvent, LinkModelSupplier, PanelEvents, toUtc, @@ -45,7 +47,6 @@ import { GraphContextMenuCtrl } from './GraphContextMenuCtrl'; import { TimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { ContextSrv } from 'app/core/services/context_srv'; import { getFieldLinksSupplier } from 'app/features/panel/panellinks/linkSuppliers'; -import { CoreEvents } from 'app/types'; const LegendWithThemeProvider = provideTheme(Legend); @@ -86,8 +87,11 @@ class GraphElement { this.ctrl.events.on(PanelEvents.render, this.onRender.bind(this)); // global events - appEvents.on(CoreEvents.graphHover, this.onGraphHover.bind(this), scope); - appEvents.on(CoreEvents.graphHoverClear, this.onGraphHoverClear.bind(this), scope); + // Using old way here to use the scope unsubscribe model as the new $on function does not take scope + this.ctrl.dashboard.events.on(LegacyGraphHoverEvent.type, this.onGraphHover.bind(this), this.scope); + this.ctrl.dashboard.events.on(LegacyGraphHoverClearEvent.type, this.onGraphHoverClear.bind(this), this.scope); + + // plot events this.elem.bind('plotselected', this.onPlotSelected.bind(this)); this.elem.bind('plotclick', this.onPlotClick.bind(this)); diff --git a/public/app/plugins/panel/graph/graph_tooltip.ts b/public/app/plugins/panel/graph/graph_tooltip.ts index 00e2b43ceeb..bc04b92291b 100644 --- a/public/app/plugins/panel/graph/graph_tooltip.ts +++ b/public/app/plugins/panel/graph/graph_tooltip.ts @@ -1,7 +1,7 @@ import $ from 'jquery'; import { appEvents } from 'app/core/core'; import { CoreEvents } from 'app/types'; -import { textUtil, systemDateFormats } from '@grafana/data'; +import { textUtil, systemDateFormats, LegacyGraphHoverClearEvent, LegacyGraphHoverEvent } from '@grafana/data'; export default function GraphTooltip(this: any, elem: any, dashboard: any, scope: any, getSeriesFn: any) { const self = this; @@ -157,7 +157,7 @@ export default function GraphTooltip(this: any, elem: any, dashboard: any, scope plot.unhighlight(); } } - appEvents.emit(CoreEvents.graphHoverClear); + dashboard.events.$emit(new LegacyGraphHoverClearEvent()); }); elem.bind('plothover', (event: any, pos: { panelRelY: number; pageY: number }, item: any) => { @@ -165,7 +165,7 @@ export default function GraphTooltip(this: any, elem: any, dashboard: any, scope // broadcast to other graph panels that we are hovering! pos.panelRelY = (pos.pageY - elem.offset().top) / elem.height(); - appEvents.emit(CoreEvents.graphHover, { pos: pos, panel: panel }); + dashboard.events.$emit(new LegacyGraphHoverEvent({ pos: pos, panel: panel })); }); elem.bind('plotclick', (event: any, pos: any, item: any) => { diff --git a/public/app/plugins/panel/graph/specs/graph.test.ts b/public/app/plugins/panel/graph/specs/graph.test.ts index f39422b0c26..6777a549782 100644 --- a/public/app/plugins/panel/graph/specs/graph.test.ts +++ b/public/app/plugins/panel/graph/specs/graph.test.ts @@ -20,13 +20,12 @@ import '../module'; import { GraphCtrl } from '../module'; import { MetricsPanelCtrl } from 'app/features/panel/metrics_panel_ctrl'; import { PanelCtrl } from 'app/features/panel/panel_ctrl'; - import config from 'app/core/config'; import TimeSeries from 'app/core/time_series2'; import $ from 'jquery'; import { graphDirective } from '../graph'; -import { dateTime } from '@grafana/data'; +import { dateTime, EventBusSrv } from '@grafana/data'; const ctx = {} as any; let ctrl: any; @@ -84,6 +83,7 @@ describe('grafanaGraph', () => { hiddenSeries: {}, dashboard: { getTimezone: () => 'browser', + events: new EventBusSrv(), }, range: { from: dateTime([2015, 1, 1, 10]), diff --git a/public/app/plugins/panel/heatmap/rendering.ts b/public/app/plugins/panel/heatmap/rendering.ts index 7c7fe30b929..1a4801c580c 100644 --- a/public/app/plugins/panel/heatmap/rendering.ts +++ b/public/app/plugins/panel/heatmap/rendering.ts @@ -1,7 +1,7 @@ import _ from 'lodash'; import $ from 'jquery'; import * as d3 from 'd3'; -import { appEvents, contextSrv } from 'app/core/core'; +import { contextSrv } from 'app/core/core'; import * as ticksUtils from 'app/core/utils/ticks'; import { HeatmapTooltip } from './heatmap_tooltip'; import { mergeZeroBuckets } from './heatmap_data_converter'; @@ -12,10 +12,11 @@ import { getValueFormat, formattedValueToString, dateTimeFormat, + LegacyGraphHoverEvent, + LegacyGraphHoverClearEvent, getColorForTheme, } from '@grafana/data'; import { graphTimeFormat } from '@grafana/ui'; -import { CoreEvents } from 'app/types'; import { config } from 'app/core/config'; const MIN_CARD_SIZE = 1, @@ -84,9 +85,8 @@ export class HeatmapRenderer { ///////////////////////////// // Shared crosshair and tooltip - appEvents.on(CoreEvents.graphHover, this.onGraphHover.bind(this), this.scope); - - appEvents.on(CoreEvents.graphHoverClear, this.onGraphHoverClear.bind(this), this.scope); + this.ctrl.dashboard.events.on(LegacyGraphHoverEvent.type, this.onGraphHover.bind(this), this.scope); + this.ctrl.dashboard.events.on(LegacyGraphHoverClearEvent.type, this.onGraphHoverClear.bind(this), this.scope); // Register selection listeners this.$heatmap.on('mousedown', this.onMouseDown.bind(this)); @@ -735,7 +735,7 @@ export class HeatmapRenderer { } onMouseLeave() { - appEvents.emit(CoreEvents.graphHoverClear); + this.ctrl.dashboard.$emit(new LegacyGraphHoverClearEvent()); this.clearCrosshair(); } @@ -781,7 +781,7 @@ export class HeatmapRenderer { // Set minimum offset to prevent showing legend from another panel pos.panelRelY = Math.max(pos.offset.y / this.height, 0.001); // broadcast to other graph panels that we are hovering - appEvents.emit(CoreEvents.graphHover, { pos: pos, panel: this.panel }); + this.ctrl.dashboard.events.emit$(new LegacyGraphHoverEvent({ pos: pos, panel: this.panel })); } limitSelection(x2: number) { diff --git a/public/app/plugins/panel/text/TextPanel.tsx b/public/app/plugins/panel/text/TextPanel.tsx index 5a7d374bdbe..ec07d0ef2fc 100644 --- a/public/app/plugins/panel/text/TextPanel.tsx +++ b/public/app/plugins/panel/text/TextPanel.tsx @@ -9,6 +9,7 @@ import { TextOptions } from './types'; import { CustomScrollbar, stylesFactory } from '@grafana/ui'; import { css, cx } from 'emotion'; import DangerouslySetHtmlContent from 'dangerously-set-html-content'; +import { Unsubscribable } from 'rxjs'; interface Props extends PanelProps {} @@ -17,6 +18,8 @@ interface State { } export class TextPanel extends PureComponent { + eventSub?: Unsubscribable; + constructor(props: Props) { super(props); diff --git a/public/app/routes/GrafanaCtrl.ts b/public/app/routes/GrafanaCtrl.ts index ffc8195dc1b..d51c2b9d4df 100644 --- a/public/app/routes/GrafanaCtrl.ts +++ b/public/app/routes/GrafanaCtrl.ts @@ -209,8 +209,6 @@ export function grafanaAppDirective( for (const drop of Drop.drops) { drop.destroy(); } - - appEvents.emit(CoreEvents.hideDashSearch); }); // handle kiosk mode diff --git a/public/app/types/events.ts b/public/app/types/events.ts index 20edb6294bc..0ad9a2bea4b 100644 --- a/public/app/types/events.ts +++ b/public/app/types/events.ts @@ -90,14 +90,13 @@ export interface DashScrollPayload { pos?: number; } +export interface PanelChangeViewPayload {} + /** * Events */ -export const showDashSearch = eventFactory('show-dash-search'); -export const hideDashSearch = eventFactory('hide-dash-search'); -export const hideDashEditor = eventFactory('hide-dash-editor'); -export const dashScroll = eventFactory('dash-scroll'); +export const panelChangeView = eventFactory('panel-change-view'); export const dashLinksUpdated = eventFactory('dash-links-updated'); export const saveDashboard = eventFactory('save-dashboard'); export const dashboardFetchStart = eventFactory('dashboard-fetch-start'); @@ -119,9 +118,6 @@ export const showModalReact = eventFactory('show-modal-re export const dsRequestResponse = eventFactory('ds-request-response'); export const dsRequestError = eventFactory('ds-request-error'); -export const graphHover = eventFactory('graph-hover'); -export const graphHoverClear = eventFactory('graph-hover-clear'); - export const toggleSidemenuMobile = eventFactory('toggle-sidemenu-mobile'); export const toggleSidemenuHidden = eventFactory('toggle-sidemenu-hidden'); diff --git a/public/app/types/explore.ts b/public/app/types/explore.ts index 9c5497ae400..995733025b0 100644 --- a/public/app/types/explore.ts +++ b/public/app/types/explore.ts @@ -15,10 +15,9 @@ import { QueryHint, RawTimeRange, TimeRange, + EventBusExtended, } from '@grafana/data'; -import { Emitter } from 'app/core/core'; - export enum ExploreId { left = 'left', right = 'right', @@ -74,7 +73,7 @@ export interface ExploreItemState { /** * Emitter to send events to the rest of Grafana. */ - eventBridge: Emitter; + eventBridge: EventBusExtended; /** * List of timeseries to be shown in the Explore graph result viewer. */ diff --git a/yarn.lock b/yarn.lock index ac9c7983e11..17cd1dbaba3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12752,6 +12752,11 @@ eventemitter3@4.0.0: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.0.tgz#d65176163887ee59f386d64c82610b696a4a74eb" integrity sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg== +eventemitter3@4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + eventemitter3@^3.1.0: version "3.1.2" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7"