dashboardWatcher.ts 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. import { Unsubscribable } from 'rxjs';
  2. import {
  3. AppEvents,
  4. isLiveChannelMessageEvent,
  5. isLiveChannelStatusEvent,
  6. LiveChannelAddress,
  7. LiveChannelConnectionState,
  8. LiveChannelEvent,
  9. LiveChannelScope,
  10. } from '@grafana/data';
  11. import { getGrafanaLiveSrv, locationService } from '@grafana/runtime';
  12. import { appEvents, contextSrv } from 'app/core/core';
  13. import { sessionId } from 'app/features/live';
  14. import { ShowModalReactEvent } from '../../../types/events';
  15. import { getDashboardSrv } from '../../dashboard/services/DashboardSrv';
  16. import { DashboardChangedModal } from './DashboardChangedModal';
  17. import { DashboardEvent, DashboardEventAction } from './types';
  18. class DashboardWatcher {
  19. channel?: LiveChannelAddress; // path to the channel
  20. uid?: string;
  21. ignoreSave?: boolean;
  22. editing = false;
  23. lastEditing?: DashboardEvent;
  24. subscription?: Unsubscribable;
  25. hasSeenNotice?: boolean;
  26. setEditingState(state: boolean) {
  27. const changed = (this.editing = state);
  28. this.editing = state;
  29. this.hasSeenNotice = false;
  30. if (changed && contextSrv.isEditor) {
  31. this.sendEditingState();
  32. }
  33. }
  34. private sendEditingState() {
  35. const { channel, uid } = this;
  36. if (channel && uid) {
  37. getGrafanaLiveSrv().publish(channel, {
  38. sessionId,
  39. uid,
  40. action: this.editing ? DashboardEventAction.EditingStarted : DashboardEventAction.EditingCanceled,
  41. timestamp: Date.now(),
  42. });
  43. }
  44. }
  45. watch(uid: string) {
  46. const live = getGrafanaLiveSrv();
  47. if (!live) {
  48. return;
  49. }
  50. // Check for changes
  51. if (uid !== this.uid) {
  52. this.channel = {
  53. scope: LiveChannelScope.Grafana,
  54. namespace: 'dashboard',
  55. path: `uid/${uid}`,
  56. };
  57. this.leave();
  58. if (uid) {
  59. this.subscription = live.getStream<DashboardEvent>(this.channel).subscribe(this.observer);
  60. }
  61. this.uid = uid;
  62. }
  63. }
  64. leave() {
  65. if (this.subscription) {
  66. this.subscription.unsubscribe();
  67. }
  68. this.subscription = undefined;
  69. this.uid = undefined;
  70. }
  71. ignoreNextSave() {
  72. this.ignoreSave = true;
  73. }
  74. getRecentEditingEvent() {
  75. if (this.lastEditing && this.lastEditing.timestamp) {
  76. const elapsed = Date.now() - this.lastEditing.timestamp;
  77. if (elapsed > 5000) {
  78. this.lastEditing = undefined;
  79. }
  80. }
  81. return this.lastEditing;
  82. }
  83. observer = {
  84. next: (event: LiveChannelEvent<DashboardEvent>) => {
  85. // Send the editing state when connection starts
  86. if (isLiveChannelStatusEvent(event) && this.editing && event.state === LiveChannelConnectionState.Connected) {
  87. this.sendEditingState();
  88. }
  89. if (isLiveChannelMessageEvent(event)) {
  90. if (event.message.sessionId === sessionId) {
  91. return; // skip internal messages
  92. }
  93. const { action } = event.message;
  94. switch (action) {
  95. case DashboardEventAction.EditingStarted:
  96. case DashboardEventAction.Saved: {
  97. if (this.ignoreSave) {
  98. this.ignoreSave = false;
  99. return;
  100. }
  101. const dash = getDashboardSrv().getCurrent();
  102. if (dash?.uid !== event.message.uid) {
  103. console.log('dashboard event for different dashboard?', event, dash);
  104. return;
  105. }
  106. const showPopup = this.editing || dash.hasUnsavedChanges();
  107. if (action === DashboardEventAction.Saved) {
  108. if (showPopup) {
  109. appEvents.publish(
  110. new ShowModalReactEvent({
  111. component: DashboardChangedModal,
  112. props: { event },
  113. })
  114. );
  115. } else {
  116. appEvents.emit(AppEvents.alertSuccess, ['Dashboard updated']);
  117. this.reloadPage();
  118. }
  119. } else if (showPopup) {
  120. if (action === DashboardEventAction.EditingStarted && !this.hasSeenNotice) {
  121. const editingEvent = event.message;
  122. const recent = this.getRecentEditingEvent();
  123. if (!recent || recent.message !== editingEvent.message) {
  124. this.hasSeenNotice = true;
  125. appEvents.emit(AppEvents.alertWarning, [
  126. 'Another session is editing this dashboard',
  127. editingEvent.message,
  128. ]);
  129. }
  130. this.lastEditing = editingEvent;
  131. }
  132. }
  133. return;
  134. }
  135. }
  136. }
  137. },
  138. };
  139. reloadPage() {
  140. locationService.reload();
  141. }
  142. }
  143. export const dashboardWatcher = new DashboardWatcher();