feat(FN-4677): add sustained-absence threshold to project validation
Fusion-Task-Id: FN-4677 Fusion-Task-Lineage: b3b9eb56-a76b-469c-9f7b-79232da29807
This commit is contained in:
committed by
gsxdsm
parent
569eb2eb79
commit
f10eac2a18
@@ -4,6 +4,7 @@ import { fetchGlobalSettings, updateGlobalSettings } from "../api";
|
|||||||
|
|
||||||
// Legacy localStorage key for migration - no longer used as primary storage
|
// Legacy localStorage key for migration - no longer used as primary storage
|
||||||
const LEGACY_STORAGE_KEY = "kb-dashboard-current-project";
|
const LEGACY_STORAGE_KEY = "kb-dashboard-current-project";
|
||||||
|
export const CONSECUTIVE_ABSENCE_THRESHOLD = 3;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the node key used in dashboardCurrentProjectIdByNode.
|
* Get the node key used in dashboardCurrentProjectIdByNode.
|
||||||
@@ -48,6 +49,8 @@ export function useCurrentProject(
|
|||||||
const explicitlyClearedRef = useRef(false);
|
const explicitlyClearedRef = useRef(false);
|
||||||
// Cache of current settings to avoid repeated fetches
|
// Cache of current settings to avoid repeated fetches
|
||||||
const settingsCacheRef = useRef<Record<string, string> | null>(null);
|
const settingsCacheRef = useRef<Record<string, string> | null>(null);
|
||||||
|
// Consecutive poll cycles where current project is missing from availableProjects
|
||||||
|
const absentCountRef = useRef(0);
|
||||||
|
|
||||||
const nodeKey = getNodeKey(nodeId);
|
const nodeKey = getNodeKey(nodeId);
|
||||||
|
|
||||||
@@ -119,6 +122,11 @@ export function useCurrentProject(
|
|||||||
};
|
};
|
||||||
}, [nodeKey, availableProjects]);
|
}, [nodeKey, availableProjects]);
|
||||||
|
|
||||||
|
// Reset absence tracking when selection changes
|
||||||
|
useEffect(() => {
|
||||||
|
absentCountRef.current = 0;
|
||||||
|
}, [currentProject?.id]);
|
||||||
|
|
||||||
// Validate project still exists and persist to global settings
|
// Validate project still exists and persist to global settings
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (loading) return;
|
if (loading) return;
|
||||||
@@ -126,10 +134,19 @@ export function useCurrentProject(
|
|||||||
if (currentProject) {
|
if (currentProject) {
|
||||||
// Validate project still exists in available projects
|
// Validate project still exists in available projects
|
||||||
const stillExists = availableProjects.some((p) => p.id === currentProject.id);
|
const stillExists = availableProjects.some((p) => p.id === currentProject.id);
|
||||||
if (!stillExists && availableProjects.length > 0) {
|
if (stillExists) {
|
||||||
// Project was unregistered - clear selection and default to first active
|
absentCountRef.current = 0;
|
||||||
const firstActive = availableProjects.find((p) => p.status === "active");
|
} else if (availableProjects.length === 0) {
|
||||||
setCurrentProjectState(firstActive || availableProjects[0] || null);
|
// Likely a transient total poll failure; keep current selection
|
||||||
|
absentCountRef.current = 0;
|
||||||
|
} else {
|
||||||
|
absentCountRef.current += 1;
|
||||||
|
if (absentCountRef.current >= CONSECUTIVE_ABSENCE_THRESHOLD) {
|
||||||
|
// Project was sustainably absent - clear selection and default to first active
|
||||||
|
absentCountRef.current = 0;
|
||||||
|
const firstActive = availableProjects.find((p) => p.status === "active");
|
||||||
|
setCurrentProjectState(firstActive || availableProjects[0] || null);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,12 +156,15 @@ export function useCurrentProject(
|
|||||||
updateGlobalSettings({ dashboardCurrentProjectIdByNode: newCache }).catch(() => {
|
updateGlobalSettings({ dashboardCurrentProjectIdByNode: newCache }).catch(() => {
|
||||||
// Non-critical - persistence failed
|
// Non-critical - persistence failed
|
||||||
});
|
});
|
||||||
} else if (availableProjects.length > 0 && !explicitlyClearedRef.current) {
|
} else {
|
||||||
// No selection but projects available - default to first active
|
absentCountRef.current = 0;
|
||||||
// Skip if user explicitly cleared (navigated to overview)
|
if (availableProjects.length > 0 && !explicitlyClearedRef.current) {
|
||||||
const firstActive = availableProjects.find((p) => p.status === "active");
|
// No selection but projects available - default to first active
|
||||||
if (firstActive) {
|
// Skip if user explicitly cleared (navigated to overview)
|
||||||
setCurrentProjectState(firstActive);
|
const firstActive = availableProjects.find((p) => p.status === "active");
|
||||||
|
if (firstActive) {
|
||||||
|
setCurrentProjectState(firstActive);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [currentProject, availableProjects, loading, nodeKey]);
|
}, [currentProject, availableProjects, loading, nodeKey]);
|
||||||
@@ -152,6 +172,7 @@ export function useCurrentProject(
|
|||||||
const setCurrentProject = useCallback(
|
const setCurrentProject = useCallback(
|
||||||
(project: ProjectInfo | null) => {
|
(project: ProjectInfo | null) => {
|
||||||
explicitlyClearedRef.current = false;
|
explicitlyClearedRef.current = false;
|
||||||
|
absentCountRef.current = 0;
|
||||||
setCurrentProjectState(project);
|
setCurrentProjectState(project);
|
||||||
|
|
||||||
if (project) {
|
if (project) {
|
||||||
@@ -167,6 +188,7 @@ export function useCurrentProject(
|
|||||||
|
|
||||||
const clearCurrentProject = useCallback(() => {
|
const clearCurrentProject = useCallback(() => {
|
||||||
explicitlyClearedRef.current = true;
|
explicitlyClearedRef.current = true;
|
||||||
|
absentCountRef.current = 0;
|
||||||
setCurrentProjectState(null);
|
setCurrentProjectState(null);
|
||||||
|
|
||||||
// Remove from cache and persist
|
// Remove from cache and persist
|
||||||
|
|||||||
Reference in New Issue
Block a user