Type-safe event bus system for application-wide communication with built-in events, custom event support, and React integration for decoupled component communication.
Core event bus implementation for publishing and subscribing to events.
/**
* Event bus service implementation
*/
class EventBusSrv implements EventBus {
/**
* Publishes an event to all subscribers
* @param event - Event instance to publish
*/
publish<T extends BusEvent>(event: T): void;
/**
* Subscribes to events of a specific type
* @param typeInfo - Event type information
* @param handler - Event handler function
* @returns Unsubscribable for cleanup
*/
subscribe<T extends BusEvent>(
typeInfo: BusEventType<T>,
handler: BusEventHandler<T>
): Unsubscribable;
/**
* Gets all subscribers for event type (for debugging)
* @param typeInfo - Event type information
* @returns Array of event handlers
*/
getEventCount<T extends BusEvent>(typeInfo: BusEventType<T>): number;
/**
* Removes all subscribers
*/
removeAllListeners(): void;
}Usage Examples:
import { EventBusSrv, DataHoverEvent } from "@grafana/data";
// Create event bus instance
const eventBus = new EventBusSrv();
// Subscribe to events
const unsubscribe = eventBus.subscribe(DataHoverEvent, (event) => {
console.log('Data hover:', event.payload);
});
// Publish events
eventBus.publish(new DataHoverEvent({
point: { x: 100, y: 200 },
data: someDataFrame
}));
// Cleanup subscription
unsubscribe.unsubscribe();Factory function for creating custom event types.
/**
* Creates event type factory for custom events
* @param name - Event name identifier
* @returns Event type factory function
*/
function eventFactory<TPayload = undefined>(name: string): BusEventType<BusEventWithPayload<TPayload>>;Usage Examples:
import { eventFactory } from "@grafana/data";
// Create custom event type
interface UserLoginPayload {
userId: string;
timestamp: number;
}
const UserLoginEvent = eventFactory<UserLoginPayload>('user-login');
// Use custom event
eventBus.subscribe(UserLoginEvent, (event) => {
console.log(`User ${event.payload.userId} logged in at ${event.payload.timestamp}`);
});
eventBus.publish(new UserLoginEvent({
userId: 'user123',
timestamp: Date.now()
}));Foundation classes for creating events with and without payloads.
/**
* Base class for events without payload
*/
class BusEventBase implements BusEvent {
/** Event type information */
readonly type: string;
/** Event origin information */
readonly origin?: any;
constructor(origin?: any);
}
/**
* Base class for events with payload
*/
class BusEventWithPayload<T> extends BusEventBase {
/** Event payload data */
readonly payload: T;
constructor(payload: T, origin?: any);
}Pre-defined events for common Grafana operations.
/**
* Data hover event for chart interactions
*/
class DataHoverEvent extends BusEventWithPayload<DataHoverPayload> {
static type = 'data-hover';
}
/**
* Data hover clear event
*/
class DataHoverClearEvent extends BusEventBase {
static type = 'data-hover-clear';
}
/**
* Data selection event for chart interactions
*/
class DataSelectEvent extends BusEventWithPayload<any> {
static type = 'data-select';
}
/**
* Annotation change event
*/
class AnnotationChangeEvent extends BusEventWithPayload<any> {
static type = 'annotation-changed';
}
/**
* Dashboard loaded event
*/
class DashboardLoadedEvent extends BusEventWithPayload<DashboardLoadedEventPayload> {
static type = 'dashboard-loaded';
}
/**
* Data source update success event
*/
class DataSourceUpdatedSuccessfully extends BusEventWithPayload<any> {
static type = 'ds-updated-successfully';
}
/**
* Data source test success event
*/
class DataSourceTestSucceeded extends BusEventWithPayload<any> {
static type = 'ds-test-succeeded';
}
/**
* Data source test failure event
*/
class DataSourceTestFailed extends BusEventWithPayload<any> {
static type = 'ds-test-failed';
}
/**
* Panel attention event
*/
class SetPanelAttentionEvent extends BusEventWithPayload<any> {
static type = 'set-panel-attention';
}/**
* Base event interface
*/
interface BusEvent {
/** Event type identifier */
readonly type: string;
/** Event origin information */
readonly origin?: any;
}
/**
* Event type information
*/
interface BusEventType<T extends BusEvent> {
/** Event type identifier */
readonly type: string;
/** Event constructor */
new (...args: any[]): T;
}
/**
* Event handler function type
*/
type BusEventHandler<T extends BusEvent> = (event: T) => void;
/**
* Event filtering options
*/
interface EventFilterOptions {
/** Only handle events from specific origin */
onlyLocal?: boolean;
}
/**
* Event bus interface
*/
interface EventBus {
/** Publish event */
publish<T extends BusEvent>(event: T): void;
/** Subscribe to events */
subscribe<T extends BusEvent>(typeInfo: BusEventType<T>, handler: BusEventHandler<T>): Unsubscribable;
/** Get subscriber count */
getEventCount?<T extends BusEvent>(typeInfo: BusEventType<T>): number;
/** Remove all listeners */
removeAllListeners?(): void;
}
/**
* Extended event bus with additional features
*/
interface EventBusExtended extends EventBus {
/** Publish with options */
publishWithOptions<T extends BusEvent>(event: T, options?: EventFilterOptions): void;
/** Subscribe with filtering */
subscribeWithFilter<T extends BusEvent>(
typeInfo: BusEventType<T>,
handler: BusEventHandler<T>,
filter?: EventFilterOptions
): Unsubscribable;
}
/**
* Application event type
*/
type AppEvent<T = any> = BusEventType<BusEventWithPayload<T>>;
/**
* Legacy emitter interface for backward compatibility
*/
interface LegacyEmitter {
emit(name: string, ...args: any[]): void;
on(name: string, handler: LegacyEventHandler<any>): void;
off(name: string, handler?: LegacyEventHandler<any>): void;
}
/**
* Legacy event handler type
*/
type LegacyEventHandler<T> = (payload?: T, ...args: any[]) => void;
/**
* Unsubscribable interface for cleanup
*/
interface Unsubscribable {
unsubscribe(): void;
}/**
* Data hover event payload
*/
interface DataHoverPayload {
/** Hover point coordinates */
point: {
x: number;
y: number;
};
/** Related data frame */
data?: DataFrame;
/** Row index */
rowIndex?: number;
/** Field index */
columnIndex?: number;
}
/**
* Dashboard loaded event payload
*/
interface DashboardLoadedEventPayload {
/** Dashboard UID */
dashboardId: string;
/** Dashboard title */
title: string;
/** Loading time in milliseconds */
loadTime?: number;
/** Dashboard metadata */
meta?: any;
}import { EventBusSrv, eventFactory } from "@grafana/data";
// Create global event bus
const globalEventBus = new EventBusSrv();
// Define custom event
interface NotificationPayload {
message: string;
type: 'success' | 'error' | 'warning' | 'info';
}
const NotificationEvent = eventFactory<NotificationPayload>('notification');
// Publisher component
class NotificationService {
constructor(private eventBus: EventBus) {}
showSuccess(message: string) {
this.eventBus.publish(new NotificationEvent({
message,
type: 'success'
}));
}
showError(message: string) {
this.eventBus.publish(new NotificationEvent({
message,
type: 'error'
}));
}
}
// Subscriber component
class NotificationDisplay {
constructor(private eventBus: EventBus) {
this.eventBus.subscribe(NotificationEvent, this.handleNotification);
}
private handleNotification = (event: BusEventWithPayload<NotificationPayload>) => {
const { message, type } = event.payload;
this.displayNotification(message, type);
};
private displayNotification(message: string, type: string) {
// Display logic here
console.log(`[${type.toUpperCase()}] ${message}`);
}
}import React, { useEffect, useState } from 'react';
import { EventBus, DataHoverEvent } from "@grafana/data";
interface Props {
eventBus: EventBus;
}
function DataHoverIndicator({ eventBus }: Props) {
const [hoverData, setHoverData] = useState<DataHoverPayload | null>(null);
useEffect(() => {
const subscription = eventBus.subscribe(DataHoverEvent, (event) => {
setHoverData(event.payload);
});
return () => subscription.unsubscribe();
}, [eventBus]);
if (!hoverData) {
return null;
}
return (
<div>
Hovering at: ({hoverData.point.x}, {hoverData.point.y})
</div>
);
}import { EventBusSrv, eventFactory } from "@grafana/data";
interface ErrorPayload {
error: Error;
context?: string;
recoverable?: boolean;
}
const ErrorEvent = eventFactory<ErrorPayload>('error');
class ErrorHandler {
constructor(private eventBus: EventBus) {
this.eventBus.subscribe(ErrorEvent, this.handleError);
}
private handleError = (event: BusEventWithPayload<ErrorPayload>) => {
const { error, context, recoverable } = event.payload;
console.error(`Error in ${context || 'unknown context'}:`, error);
if (!recoverable) {
// Handle critical errors
this.handleCriticalError(error);
} else {
// Handle recoverable errors
this.handleRecoverableError(error);
}
};
private handleCriticalError(error: Error) {
// Critical error handling logic
}
private handleRecoverableError(error: Error) {
// Recoverable error handling logic
}
}
// Usage
const errorHandler = new ErrorHandler(globalEventBus);
// Publish error
try {
// Some operation
} catch (error) {
globalEventBus.publish(new ErrorEvent({
error,
context: 'data processing',
recoverable: true
}));
}