grafana/public/app/features/live/dashboard/dashboardWatcher.ts

161 lines
4.6 KiB
TypeScript

import { getGrafanaLiveSrv, locationService } from '@grafana/runtime';
import { getDashboardSrv } from '../../dashboard/services/DashboardSrv';
import { appEvents, contextSrv } from 'app/core/core';
import {
AppEvents,
isLiveChannelMessageEvent,
isLiveChannelStatusEvent,
LiveChannelAddress,
LiveChannelConnectionState,
LiveChannelEvent,
LiveChannelScope,
} from '@grafana/data';
import { DashboardChangedModal } from './DashboardChangedModal';
import { DashboardEvent, DashboardEventAction } from './types';
import { sessionId } from 'app/features/live';
import { ShowModalReactEvent } from '../../../types/events';
import { Unsubscribable } from 'rxjs';
class DashboardWatcher {
channel?: LiveChannelAddress; // path to the channel
uid?: string;
ignoreSave?: boolean;
editing = false;
lastEditing?: DashboardEvent;
subscription?: Unsubscribable;
hasSeenNotice?: boolean;
setEditingState(state: boolean) {
const changed = (this.editing = state);
this.editing = state;
this.hasSeenNotice = false;
if (changed && contextSrv.isEditor) {
this.sendEditingState();
}
}
private sendEditingState() {
const { channel, uid } = this;
if (channel && uid) {
getGrafanaLiveSrv().publish(channel, {
sessionId,
uid,
action: this.editing ? DashboardEventAction.EditingStarted : DashboardEventAction.EditingCanceled,
timestamp: Date.now(),
});
}
}
watch(uid: string) {
const live = getGrafanaLiveSrv();
if (!live) {
return;
}
// Check for changes
if (uid !== this.uid) {
this.channel = {
scope: LiveChannelScope.Grafana,
namespace: 'dashboard',
path: `uid/${uid}`,
};
this.leave();
if (uid) {
this.subscription = live.getStream<DashboardEvent>(this.channel).subscribe(this.observer);
}
this.uid = uid;
}
}
leave() {
if (this.subscription) {
this.subscription.unsubscribe();
}
this.subscription = undefined;
this.uid = undefined;
}
ignoreNextSave() {
this.ignoreSave = true;
}
getRecentEditingEvent() {
if (this.lastEditing && this.lastEditing.timestamp) {
const elapsed = Date.now() - this.lastEditing.timestamp;
if (elapsed > 5000) {
this.lastEditing = undefined;
}
}
return this.lastEditing;
}
observer = {
next: (event: LiveChannelEvent<DashboardEvent>) => {
// Send the editing state when connection starts
if (isLiveChannelStatusEvent(event) && this.editing && event.state === LiveChannelConnectionState.Connected) {
this.sendEditingState();
}
if (isLiveChannelMessageEvent(event)) {
if (event.message.sessionId === sessionId) {
return; // skip internal messages
}
const { action } = event.message;
switch (action) {
case DashboardEventAction.EditingStarted:
case DashboardEventAction.Saved: {
if (this.ignoreSave) {
this.ignoreSave = false;
return;
}
const dash = getDashboardSrv().getCurrent();
if (dash?.uid !== event.message.uid) {
console.log('dashboard event for different dashboard?', event, dash);
return;
}
const showPopup = this.editing || dash.hasUnsavedChanges();
if (action === DashboardEventAction.Saved) {
if (showPopup) {
appEvents.publish(
new ShowModalReactEvent({
component: DashboardChangedModal,
props: { event },
})
);
} else {
appEvents.emit(AppEvents.alertSuccess, ['Dashboard updated']);
this.reloadPage();
}
} else if (showPopup) {
if (action === DashboardEventAction.EditingStarted && !this.hasSeenNotice) {
const editingEvent = event.message;
const recent = this.getRecentEditingEvent();
if (!recent || recent.message !== editingEvent.message) {
this.hasSeenNotice = true;
appEvents.emit(AppEvents.alertWarning, [
'Another session is editing this dashboard',
editingEvent.message,
]);
}
this.lastEditing = editingEvent;
}
}
return;
}
}
}
},
};
reloadPage() {
locationService.reload();
}
}
export const dashboardWatcher = new DashboardWatcher();