feat(FN-4306): complete Step 3 — gate and dismiss capacity risk banner
Fusion-Task-Id: FN-4306 Fusion-Task-Lineage: b0ab9b60-de02-40a7-ad8b-0ca18c98e16c
This commit is contained in:
@@ -76,7 +76,7 @@ import { ShellConnectionStatus } from "./components/ShellConnectionStatus";
|
|||||||
import { getShellConnectionNativeResult, type ShellConnectionNativeResult } from "./shell-native";
|
import { getShellConnectionNativeResult, type ShellConnectionNativeResult } from "./shell-native";
|
||||||
import type { AiSessionSummary, DashboardHealthResponse } from "./api";
|
import type { AiSessionSummary, DashboardHealthResponse } from "./api";
|
||||||
import { api, fetchDashboardHealth, fetchUnreadCount, fetchTaskDetail, fetchWorkflowSteps } from "./api";
|
import { api, fetchDashboardHealth, fetchUnreadCount, fetchTaskDetail, fetchWorkflowSteps } from "./api";
|
||||||
import { getScopedItem, setScopedItem } from "./utils/projectStorage";
|
import { getScopedItem, removeScopedItem, setScopedItem } from "./utils/projectStorage";
|
||||||
import { subscribeSse } from "./sse-bus";
|
import { subscribeSse } from "./sse-bus";
|
||||||
import { AUTH_TOKEN_RECOVERY_REQUIRED_EVENT } from "./auth";
|
import { AUTH_TOKEN_RECOVERY_REQUIRED_EVENT } from "./auth";
|
||||||
import { AuthTokenRecoveryDialog } from "./components/AuthTokenRecoveryDialog";
|
import { AuthTokenRecoveryDialog } from "./components/AuthTokenRecoveryDialog";
|
||||||
@@ -140,6 +140,7 @@ const WORKING_BRANCH_FILTER_STORAGE_KEY = "kb-dashboard-working-branch-filter";
|
|||||||
const BASE_BRANCH_FILTER_STORAGE_KEY = "kb-dashboard-base-branch-filter";
|
const BASE_BRANCH_FILTER_STORAGE_KEY = "kb-dashboard-base-branch-filter";
|
||||||
const NO_BRANCH_FILTER_VALUE = "__fusion:no-branch__";
|
const NO_BRANCH_FILTER_VALUE = "__fusion:no-branch__";
|
||||||
const APPROVAL_BANNER_DISMISSED_STORAGE_KEY = "fusion:approval-banner-dismissed";
|
const APPROVAL_BANNER_DISMISSED_STORAGE_KEY = "fusion:approval-banner-dismissed";
|
||||||
|
const CAPACITY_RISK_DISMISSED_KEY = "kb-capacity-risk-banner-dismissed";
|
||||||
|
|
||||||
interface ApprovalBannerCandidate {
|
interface ApprovalBannerCandidate {
|
||||||
dedupeKey: string;
|
dedupeKey: string;
|
||||||
@@ -665,6 +666,9 @@ function AppInner() {
|
|||||||
const [setupWarningDismissed, setSetupWarningDismissed] = useState(
|
const [setupWarningDismissed, setSetupWarningDismissed] = useState(
|
||||||
() => getScopedItem(SETUP_WARNING_DISMISSED_KEY, currentProject?.id) === "true",
|
() => getScopedItem(SETUP_WARNING_DISMISSED_KEY, currentProject?.id) === "true",
|
||||||
);
|
);
|
||||||
|
const [capacityRiskDismissed, setCapacityRiskDismissed] = useState(
|
||||||
|
() => getScopedItem(CAPACITY_RISK_DISMISSED_KEY, currentProject?.id) === "true",
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSetupWarningDismissed(
|
setSetupWarningDismissed(
|
||||||
@@ -672,6 +676,12 @@ function AppInner() {
|
|||||||
);
|
);
|
||||||
}, [currentProject?.id]);
|
}, [currentProject?.id]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setCapacityRiskDismissed(
|
||||||
|
getScopedItem(CAPACITY_RISK_DISMISSED_KEY, currentProject?.id) === "true",
|
||||||
|
);
|
||||||
|
}, [currentProject?.id]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
|
|
||||||
@@ -708,6 +718,11 @@ function AppInner() {
|
|||||||
setSetupWarningDismissed(true);
|
setSetupWarningDismissed(true);
|
||||||
}, [currentProject?.id]);
|
}, [currentProject?.id]);
|
||||||
|
|
||||||
|
const handleDismissCapacityRisk = useCallback(() => {
|
||||||
|
setScopedItem(CAPACITY_RISK_DISMISSED_KEY, "true", currentProject?.id);
|
||||||
|
setCapacityRiskDismissed(true);
|
||||||
|
}, [currentProject?.id]);
|
||||||
|
|
||||||
// Settings state
|
// Settings state
|
||||||
const {
|
const {
|
||||||
maxConcurrent,
|
maxConcurrent,
|
||||||
@@ -716,6 +731,7 @@ function AppInner() {
|
|||||||
enginePaused,
|
enginePaused,
|
||||||
taskStuckTimeoutMs,
|
taskStuckTimeoutMs,
|
||||||
staleHighFanoutBlockerAgeThresholdMs,
|
staleHighFanoutBlockerAgeThresholdMs,
|
||||||
|
capacityRiskBannerEnabled,
|
||||||
capacityRiskTodoThreshold,
|
capacityRiskTodoThreshold,
|
||||||
showQuickChatFAB,
|
showQuickChatFAB,
|
||||||
prAuthAvailable,
|
prAuthAvailable,
|
||||||
@@ -753,6 +769,33 @@ function AppInner() {
|
|||||||
[agentStats?.todoTaskCount, agentStats?.idleNonEphemeralCount, inProgressCount, inReviewCount, capacityRiskTodoThreshold],
|
[agentStats?.todoTaskCount, agentStats?.idleNonEphemeralCount, inProgressCount, inReviewCount, capacityRiskTodoThreshold],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const previousCapacityRiskBannerEnabledRef = useRef(capacityRiskBannerEnabled);
|
||||||
|
const previousCapacityRiskTodoThresholdRef = useRef(capacityRiskTodoThreshold);
|
||||||
|
const previousCapacityRiskProjectIdRef = useRef(currentProject?.id);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (previousCapacityRiskProjectIdRef.current !== currentProject?.id) {
|
||||||
|
previousCapacityRiskProjectIdRef.current = currentProject?.id;
|
||||||
|
previousCapacityRiskBannerEnabledRef.current = capacityRiskBannerEnabled;
|
||||||
|
previousCapacityRiskTodoThresholdRef.current = capacityRiskTodoThreshold;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const wasEnabled = previousCapacityRiskBannerEnabledRef.current;
|
||||||
|
const previousThreshold = previousCapacityRiskTodoThresholdRef.current;
|
||||||
|
const bannerEnabledChangedToTrue = !wasEnabled && capacityRiskBannerEnabled;
|
||||||
|
const thresholdChanged = previousThreshold !== capacityRiskTodoThreshold;
|
||||||
|
|
||||||
|
if (bannerEnabledChangedToTrue || thresholdChanged) {
|
||||||
|
removeScopedItem(CAPACITY_RISK_DISMISSED_KEY, currentProject?.id);
|
||||||
|
setCapacityRiskDismissed(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
previousCapacityRiskProjectIdRef.current = currentProject?.id;
|
||||||
|
previousCapacityRiskBannerEnabledRef.current = capacityRiskBannerEnabled;
|
||||||
|
previousCapacityRiskTodoThresholdRef.current = capacityRiskTodoThreshold;
|
||||||
|
}, [capacityRiskBannerEnabled, capacityRiskTodoThreshold, currentProject?.id]);
|
||||||
|
|
||||||
const skillsEnabled = experimentalFeatures.skillsView === true;
|
const skillsEnabled = experimentalFeatures.skillsView === true;
|
||||||
const nodesEnabled = experimentalFeatures.nodesView === true;
|
const nodesEnabled = experimentalFeatures.nodesView === true;
|
||||||
const researchEnabled = experimentalFeatures.researchView === true;
|
const researchEnabled = experimentalFeatures.researchView === true;
|
||||||
@@ -1463,7 +1506,9 @@ function AppInner() {
|
|||||||
if (taskView === "board") {
|
if (taskView === "board") {
|
||||||
return (
|
return (
|
||||||
<PageErrorBoundary>
|
<PageErrorBoundary>
|
||||||
<CapacityRiskBanner signal={capacityRiskSignal} />
|
{capacityRiskBannerEnabled && !capacityRiskDismissed ? (
|
||||||
|
<CapacityRiskBanner signal={capacityRiskSignal} onDismiss={handleDismissCapacityRisk} />
|
||||||
|
) : null}
|
||||||
<Board
|
<Board
|
||||||
tasks={filteredBoardTasks}
|
tasks={filteredBoardTasks}
|
||||||
projectId={currentProject?.id}
|
projectId={currentProject?.id}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export interface UseAppSettingsResult {
|
|||||||
enginePaused: boolean;
|
enginePaused: boolean;
|
||||||
taskStuckTimeoutMs: number | undefined;
|
taskStuckTimeoutMs: number | undefined;
|
||||||
staleHighFanoutBlockerAgeThresholdMs: number;
|
staleHighFanoutBlockerAgeThresholdMs: number;
|
||||||
|
capacityRiskBannerEnabled: boolean;
|
||||||
capacityRiskTodoThreshold: number;
|
capacityRiskTodoThreshold: number;
|
||||||
showQuickChatFAB: boolean;
|
showQuickChatFAB: boolean;
|
||||||
prAuthAvailable: boolean;
|
prAuthAvailable: boolean;
|
||||||
@@ -43,6 +44,7 @@ export function useAppSettings(projectId?: string): UseAppSettingsResult {
|
|||||||
const [enginePaused, setEnginePaused] = useState(false);
|
const [enginePaused, setEnginePaused] = useState(false);
|
||||||
const [taskStuckTimeoutMs, setTaskStuckTimeoutMs] = useState<number | undefined>(undefined);
|
const [taskStuckTimeoutMs, setTaskStuckTimeoutMs] = useState<number | undefined>(undefined);
|
||||||
const [staleHighFanoutBlockerAgeThresholdMs, setStaleHighFanoutBlockerAgeThresholdMs] = useState(2 * 60 * 60 * 1000);
|
const [staleHighFanoutBlockerAgeThresholdMs, setStaleHighFanoutBlockerAgeThresholdMs] = useState(2 * 60 * 60 * 1000);
|
||||||
|
const [capacityRiskBannerEnabled, setCapacityRiskBannerEnabled] = useState(false);
|
||||||
const [capacityRiskTodoThreshold, setCapacityRiskTodoThreshold] = useState(20);
|
const [capacityRiskTodoThreshold, setCapacityRiskTodoThreshold] = useState(20);
|
||||||
const [showQuickChatFAB, setShowQuickChatFAB] = useState(false);
|
const [showQuickChatFAB, setShowQuickChatFAB] = useState(false);
|
||||||
const [prAuthAvailable, setPrAuthAvailable] = useState(false);
|
const [prAuthAvailable, setPrAuthAvailable] = useState(false);
|
||||||
@@ -80,6 +82,7 @@ export function useAppSettings(projectId?: string): UseAppSettingsResult {
|
|||||||
settings.staleHighFanoutBlockerAgeThresholdMs ?? 2 * 60 * 60 * 1000,
|
settings.staleHighFanoutBlockerAgeThresholdMs ?? 2 * 60 * 60 * 1000,
|
||||||
);
|
);
|
||||||
setShowQuickChatFAB(settings.showQuickChatFAB === true);
|
setShowQuickChatFAB(settings.showQuickChatFAB === true);
|
||||||
|
setCapacityRiskBannerEnabled(settings.capacityRiskBannerEnabled === true);
|
||||||
setCapacityRiskTodoThreshold(settings.capacityRiskTodoThreshold ?? 20);
|
setCapacityRiskTodoThreshold(settings.capacityRiskTodoThreshold ?? 20);
|
||||||
setExperimentalFeatures(settings.experimentalFeatures ?? {});
|
setExperimentalFeatures(settings.experimentalFeatures ?? {});
|
||||||
const features = settings.experimentalFeatures ?? {};
|
const features = settings.experimentalFeatures ?? {};
|
||||||
@@ -177,6 +180,7 @@ export function useAppSettings(projectId?: string): UseAppSettingsResult {
|
|||||||
enginePaused,
|
enginePaused,
|
||||||
taskStuckTimeoutMs,
|
taskStuckTimeoutMs,
|
||||||
staleHighFanoutBlockerAgeThresholdMs,
|
staleHighFanoutBlockerAgeThresholdMs,
|
||||||
|
capacityRiskBannerEnabled,
|
||||||
capacityRiskTodoThreshold,
|
capacityRiskTodoThreshold,
|
||||||
showQuickChatFAB,
|
showQuickChatFAB,
|
||||||
prAuthAvailable,
|
prAuthAvailable,
|
||||||
|
|||||||
Reference in New Issue
Block a user