Fix issue with persisting profiles drilldown panels
This commit is contained in:
@@ -1657,23 +1657,25 @@ export class CRDTStateManager {
|
||||
const panelData: CRDTExploreMapStateJSON['panelData'] = {};
|
||||
|
||||
for (const [panelId, data] of this.state.panelData.entries()) {
|
||||
panelData[panelId] = {
|
||||
id: data.id,
|
||||
exploreId: data.exploreId,
|
||||
positionX: data.positionX.toJSON(),
|
||||
positionY: data.positionY.toJSON(),
|
||||
width: data.width.toJSON(),
|
||||
height: data.height.toJSON(),
|
||||
zIndex: data.zIndex.toJSON(),
|
||||
exploreState: data.exploreState.toJSON(),
|
||||
mode: data.mode.toJSON(),
|
||||
iframeUrl: data.iframeUrl.toJSON(),
|
||||
createdBy: data.createdBy.toJSON(),
|
||||
frameId: data.frameId.toJSON(),
|
||||
frameOffsetX: data.frameOffsetX.toJSON(),
|
||||
frameOffsetY: data.frameOffsetY.toJSON(),
|
||||
remoteVersion: data.remoteVersion,
|
||||
};
|
||||
if (this.state.panels.contains(panelId)) {
|
||||
panelData[panelId] = {
|
||||
id: data.id,
|
||||
exploreId: data.exploreId,
|
||||
positionX: data.positionX.toJSON(),
|
||||
positionY: data.positionY.toJSON(),
|
||||
width: data.width.toJSON(),
|
||||
height: data.height.toJSON(),
|
||||
zIndex: data.zIndex.toJSON(),
|
||||
exploreState: data.exploreState.toJSON(),
|
||||
mode: data.mode.toJSON(),
|
||||
iframeUrl: data.iframeUrl.toJSON(),
|
||||
createdBy: data.createdBy.toJSON(),
|
||||
frameId: data.frameId.toJSON(),
|
||||
frameOffsetX: data.frameOffsetX.toJSON(),
|
||||
frameOffsetY: data.frameOffsetY.toJSON(),
|
||||
remoteVersion: data.remoteVersion,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const commentData: Record<string, CommentData> = {};
|
||||
|
||||
@@ -13,6 +13,7 @@ import { ExploreMapState, initialExploreMapState, SerializedExploreState } from
|
||||
|
||||
const STORAGE_KEY = 'grafana.exploreMap.state';
|
||||
const AUTO_SAVE_DELAY_MS = 2000;
|
||||
const MAX_SAVE_DELAY_MS = 5000; // Maximum time to wait before forcing a save
|
||||
|
||||
interface UseMapPersistenceOptions {
|
||||
uid?: string; // If provided, load from and save to API. If not, use localStorage (legacy mode)
|
||||
@@ -28,8 +29,10 @@ export function useCanvasPersistence(options: UseMapPersistenceOptions = {}) {
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [lastSaved, setLastSaved] = useState<Date | null>(null);
|
||||
const saveTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const maxWaitTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const initialLoadDone = useRef(false);
|
||||
const lastSavedCRDTStateRef = useRef<string | null | undefined>(null);
|
||||
const firstChangeTimeRef = useRef<number | null>(null);
|
||||
|
||||
// Helper to enrich state with Explore pane data
|
||||
const enrichStateWithExploreData = useCallback(
|
||||
@@ -73,7 +76,6 @@ export function useCanvasPersistence(options: UseMapPersistenceOptions = {}) {
|
||||
if (initialLoadDone.current) {
|
||||
return;
|
||||
}
|
||||
initialLoadDone.current = true;
|
||||
|
||||
const loadState = async () => {
|
||||
if (uid) {
|
||||
@@ -106,6 +108,8 @@ export function useCanvasPersistence(options: UseMapPersistenceOptions = {}) {
|
||||
if (parsed.crdtState) {
|
||||
// Load the saved CRDT state which includes proper OR-Set metadata
|
||||
dispatch(loadCRDTState({ crdtState: parsed.crdtState }));
|
||||
// Store the loaded state as the last saved state to prevent immediate re-save
|
||||
lastSavedCRDTStateRef.current = JSON.stringify(parsed.crdtState);
|
||||
} else {
|
||||
// Fallback to legacy initialization for backward compatibility
|
||||
dispatch(initializeFromLegacyState({
|
||||
@@ -128,6 +132,8 @@ export function useCanvasPersistence(options: UseMapPersistenceOptions = {}) {
|
||||
}, 2000);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
// Mark initial load as done AFTER loading completes
|
||||
initialLoadDone.current = true;
|
||||
}
|
||||
} else {
|
||||
// Load from localStorage (legacy mode)
|
||||
@@ -144,6 +150,8 @@ export function useCanvasPersistence(options: UseMapPersistenceOptions = {}) {
|
||||
if (parsed.crdtState) {
|
||||
// Load the saved CRDT state which includes proper OR-Set metadata
|
||||
dispatch(loadCRDTState({ crdtState: parsed.crdtState }));
|
||||
// Store the loaded state as the last saved state to prevent immediate re-save
|
||||
lastSavedCRDTStateRef.current = JSON.stringify(parsed.crdtState);
|
||||
} else {
|
||||
// Fallback to legacy initialization for backward compatibility
|
||||
dispatch(initializeFromLegacyState({
|
||||
@@ -157,6 +165,8 @@ export function useCanvasPersistence(options: UseMapPersistenceOptions = {}) {
|
||||
} catch (error) {
|
||||
console.error('Failed to load canvas state from storage:', error);
|
||||
}
|
||||
// Mark initial load as done immediately for localStorage (no async delay)
|
||||
initialLoadDone.current = true;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -190,11 +200,22 @@ export function useCanvasPersistence(options: UseMapPersistenceOptions = {}) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear any pending save
|
||||
// Track when the first change happened for max-wait enforcement
|
||||
const now = Date.now();
|
||||
if (firstChangeTimeRef.current === null) {
|
||||
firstChangeTimeRef.current = now;
|
||||
}
|
||||
|
||||
// Clear any pending save timeout before scheduling a new one
|
||||
if (saveTimeoutRef.current) {
|
||||
clearTimeout(saveTimeoutRef.current);
|
||||
saveTimeoutRef.current = null;
|
||||
}
|
||||
|
||||
// Check if we've exceeded the maximum wait time
|
||||
const timeSinceFirstChange = now - (firstChangeTimeRef.current || now);
|
||||
const shouldForceImmediateSave = timeSinceFirstChange >= MAX_SAVE_DELAY_MS;
|
||||
|
||||
const saveState = async () => {
|
||||
// CRDT panels already contain exploreState from savePanelExploreState actions
|
||||
// We don't need to enrich them with live Explore pane data
|
||||
@@ -214,45 +235,51 @@ export function useCanvasPersistence(options: UseMapPersistenceOptions = {}) {
|
||||
crdtState: crdtState.crdtStateJSON ? JSON.parse(crdtState.crdtStateJSON) : undefined,
|
||||
};
|
||||
|
||||
const performSave = async () => {
|
||||
try {
|
||||
setSaving(true);
|
||||
const titleToSave = mapTitle || 'Untitled Map';
|
||||
const dataToSave = enrichedState;
|
||||
await exploreMapApi.updateExploreMap(uid!, {
|
||||
title: titleToSave,
|
||||
data: dataToSave,
|
||||
});
|
||||
setLastSaved(new Date());
|
||||
// Update last saved state ref to prevent duplicate saves
|
||||
lastSavedCRDTStateRef.current = currentCRDTStateStr;
|
||||
// Reset the first change timer after successful save
|
||||
firstChangeTimeRef.current = null;
|
||||
} catch (error) {
|
||||
console.error('Failed to save atlas:', error);
|
||||
dispatch(notifyApp(createErrorNotification('Failed to save atlas', 'Changes may not be persisted')));
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (uid) {
|
||||
// Save to API with debounce
|
||||
saveTimeoutRef.current = setTimeout(async () => {
|
||||
try {
|
||||
setSaving(true);
|
||||
const titleToSave = mapTitle || 'Untitled Map';
|
||||
const dataToSave = enrichedState;
|
||||
await exploreMapApi.updateExploreMap(uid, {
|
||||
title: titleToSave,
|
||||
data: dataToSave,
|
||||
});
|
||||
setLastSaved(new Date());
|
||||
// Update last saved state ref to prevent duplicate saves
|
||||
lastSavedCRDTStateRef.current = currentCRDTStateStr;
|
||||
} catch (error) {
|
||||
dispatch(notifyApp(createErrorNotification('Failed to save atlas', 'Changes may not be persisted')));
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
}, AUTO_SAVE_DELAY_MS);
|
||||
if (shouldForceImmediateSave) {
|
||||
// Force immediate save if max wait time exceeded
|
||||
performSave();
|
||||
} else {
|
||||
saveTimeoutRef.current = setTimeout(performSave, AUTO_SAVE_DELAY_MS);
|
||||
}
|
||||
} else {
|
||||
// Save to localStorage immediately (legacy mode)
|
||||
try {
|
||||
store.set(STORAGE_KEY, JSON.stringify(enrichedState));
|
||||
// Update last saved state ref to prevent duplicate saves
|
||||
lastSavedCRDTStateRef.current = currentCRDTStateStr;
|
||||
// Reset the first change timer after successful save
|
||||
firstChangeTimeRef.current = null;
|
||||
} catch (error) {
|
||||
console.error('Failed to save to localStorage:', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
saveState();
|
||||
|
||||
// Cleanup timeout on unmount
|
||||
return () => {
|
||||
if (saveTimeoutRef.current) {
|
||||
clearTimeout(saveTimeoutRef.current);
|
||||
}
|
||||
};
|
||||
}, [crdtState, dispatch, loading, uid]);
|
||||
|
||||
const exportCanvas = useCallback(() => {
|
||||
|
||||
Reference in New Issue
Block a user