Skip to content

Conversation

@will-holley
Copy link
Contributor

@will-holley will-holley commented Oct 20, 2025

Summary

This PR is a minor refactor of the useApiHealth hook, backing it with an app-scoped React Context TanStack Query instance that polls for status updates.

Motivation

While working on #5869, I observed that we're making unnecessarily redundant checkHealth calls.

@will-holley will-holley requested a review from mldangelo October 20, 2025 17:41
@will-holley will-holley self-assigned this Oct 20, 2025
@will-holley will-holley marked this pull request as ready for review October 20, 2025 17:41
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 20, 2025

📝 Walkthrough

Walkthrough

This PR refactors API health monitoring from a custom hook pattern to a React Context-based pattern. It introduces ApiHealthContext and ApiHealthProvider to centralize API health checks with automatic polling every 3 seconds using a new usePolling utility hook. The old useApiHealth hook file is deleted and replaced with context-based imports throughout the codebase. Individual components are simplified by removing on-mount checkHealth calls, relying instead on the centralized context for status updates. Test files are updated to mock the new context module path. The provider includes AbortController logic to cancel prior requests and handles various API response scenarios (DISABLED, OK, errors).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

This refactor involves architectural changes across multiple file types—import migrations (repetitive, low complexity), a new context implementation with polling and AbortController management (moderate logic density), a comprehensive test suite for the context (higher density), and component simplifications (straightforward removals). While many changes follow consistent patterns, the new context implementation, its error-handling logic, and the expanded test coverage require careful review to validate the architectural shift and ensure no regressions.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed The pull request description is directly related to the changeset. The author describes refactoring the useApiHealth hook by backing it with an app-scoped context that polls for status updates, which accurately reflects the substantial changes shown in the raw summary: the old useApiHealth.tsx hook file is deleted, a new ApiHealthContext.tsx with provider is created, a usePolling hook is introduced for periodic polling, and imports are updated throughout the codebase. The motivation about eliminating redundant checkHealth calls also aligns with the pattern observed in the changeset where explicit checkHealth calls are removed from multiple components (Plugins, PluginsTab, Review, CustomPoliciesSection). The description provides meaningful context about the nature and purpose of the refactoring.
Title Check ✅ Passed The PR title "refactor(app): Reduce unnecessary API health requests" directly and accurately summarizes the primary objective of this changeset. The PR refactors the API health monitoring system from a hook-based approach where each component independently called checkHealth on mount, to a centralized context-provider pattern with app-level polling. This architectural shift is specifically designed to eliminate the redundant API health requests that were occurring across multiple components. The title is concise, specific, and clearly communicates the core improvement without unnecessary details or vague language.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/api-health-context

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/app/src/pages/launcher/page.tsx (1)

137-153: Remove redundant polling logic.

This component implements its own polling mechanism (every 2 seconds), but the ApiHealthProvider already polls every 3 seconds globally. This creates duplicate API health check requests and could lead to race conditions or unexpected behavior.

Since the component already receives status from the context (line 98), the polling effect should be removed and the component should rely entirely on the context's automatic polling.

Apply this diff to remove the redundant polling:

-  useEffect(() => {
-    // Simple delay to allow server startup, then start health checks
-    let interval: NodeJS.Timeout;
-    const timeout = setTimeout(() => {
-      checkHealth();
-      interval = setInterval(() => {
-        checkHealth();
-      }, 2000);
-    }, 3000);
-
-    return () => {
-      clearTimeout(timeout);
-      if (interval) {
-        clearInterval(interval);
-      }
-    };
-  }, [checkHealth]);

The ApiHealthProvider will handle polling automatically, and the status updates will flow through the context to this component.

🧹 Nitpick comments (3)
src/app/src/pages/redteam/setup/components/Targets/CustomPoliciesSection.tsx (1)

161-162: Surface "checking" state in tooltip to improve UX

Right now, when status is not connected, the tooltip always says connection is required. Consider showing a “Checking connection...” message while health is being checked.

Apply:

-  const { status: apiHealthStatus } = useApiHealth();
+  const { status: apiHealthStatus, isChecking } = useApiHealth();
-  <TestCaseGenerateButton
+  <TestCaseGenerateButton
     onClick={() => handleGenerateTestCase(params.row)}
     disabled={
       apiHealthStatus !== 'connected' ||
       (generatingTestCase && generatingPolicyIndex === params.row.id)
     }
     isGenerating={generatingTestCase && generatingPolicyIndex === params.row.id}
-    tooltipTitle={
-      apiHealthStatus === 'connected'
-        ? undefined
-        : 'Promptfoo Cloud connection is required for test generation'
-    }
+    tooltipTitle={
+      apiHealthStatus === 'connected'
+        ? undefined
+        : isChecking
+          ? 'Checking connection to Promptfoo Cloud...'
+          : 'Promptfoo Cloud connection is required for test generation'
+    }
   />

Also applies to: 492-504

src/app/src/contexts/ApiHealthContext.test.tsx (1)

64-72: Optional: add a test for an initial health check on mount

If we make usePolling trigger a leading-edge call, add a test to assert checkHealth runs once on mount. This protects UX regressions where status stays unknown for the first interval.

Example addition:

 describe('ApiHealthProvider', () => {
   it('should initialize with default state', () => {
@@
   });
 
+  it('should invoke a health check immediately on mount', async () => {
+    // Make usePolling call the provided function right away
+    vi.mocked(usePolling).mockImplementation((fn: any) => { fn(); });
+    const wrapper = ({ children }: { children: ReactNode }) => (
+      <ApiHealthProvider>{children}</ApiHealthProvider>
+    );
+    const mockResponse = { json: vi.fn().mockResolvedValue({ status: 'OK', message: '' }) };
+    vi.mocked(callApi).mockResolvedValue(mockResponse as any);
+    const { result } = renderHook(() => useApiHealth(), { wrapper });
+    await waitFor(() => expect(result.current.status).toBe('connected'));
+  });
src/app/src/contexts/ApiHealthContext.tsx (1)

77-77: Consider invoking checkHealth immediately on mount.

The usePolling hook only sets up the interval without an initial invocation, so the first health check happens after 3 seconds. This means users see an 'unknown' status for 3 seconds after the app loads.

Add an effect to call checkHealth immediately:

+import { createContext, useContext, useState, useRef, useCallback, useEffect } from 'react';
+
 export function ApiHealthProvider({ children }: { children: React.ReactNode }) {
   // ... existing state and checkHealth code ...
 
+  // Check health immediately on mount
+  useEffect(() => {
+    checkHealth();
+  }, [checkHealth]);
+
   usePolling(() => checkHealth(), API_POLLING_INTERVAL, []);
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f142014 and c9287e3.

📒 Files selected for processing (17)
  • src/app/src/App.tsx (2 hunks)
  • src/app/src/components/ApiSettingsModal.test.tsx (4 hunks)
  • src/app/src/components/ApiSettingsModal.tsx (1 hunks)
  • src/app/src/contexts/ApiHealthContext.test.tsx (1 hunks)
  • src/app/src/contexts/ApiHealthContext.tsx (1 hunks)
  • src/app/src/hooks/useApiHealth.test.tsx (0 hunks)
  • src/app/src/hooks/useApiHealth.tsx (0 hunks)
  • src/app/src/hooks/usePolling.ts (1 hunks)
  • src/app/src/pages/launcher/page.test.tsx (2 hunks)
  • src/app/src/pages/launcher/page.tsx (1 hunks)
  • src/app/src/pages/redteam/setup/components/Plugins.tsx (2 hunks)
  • src/app/src/pages/redteam/setup/components/PluginsTab.tsx (3 hunks)
  • src/app/src/pages/redteam/setup/components/Purpose.test.tsx (1 hunks)
  • src/app/src/pages/redteam/setup/components/Purpose.tsx (1 hunks)
  • src/app/src/pages/redteam/setup/components/Review.test.tsx (4 hunks)
  • src/app/src/pages/redteam/setup/components/Review.tsx (2 hunks)
  • src/app/src/pages/redteam/setup/components/Targets/CustomPoliciesSection.tsx (2 hunks)
💤 Files with no reviewable changes (2)
  • src/app/src/hooks/useApiHealth.test.tsx
  • src/app/src/hooks/useApiHealth.tsx
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/gh-cli-workflow.mdc)

Prefer not to introduce new TypeScript types; use existing interfaces whenever possible

**/*.{ts,tsx}: Use TypeScript with strict type checking
Follow consistent import order (Biome will handle import sorting)
Use consistent curly braces for all control statements
Prefer const over let; avoid var
Use object shorthand syntax whenever possible
Use async/await for asynchronous code
Use consistent error handling with proper type checks
Always sanitize sensitive data before logging
Use structured logger methods (debug/info/warn/error) with a context object instead of interpolating secrets into log strings
Use sanitizeObject for manual sanitization in non-logging contexts before persisting or further processing data

Files:

  • src/app/src/App.tsx
  • src/app/src/components/ApiSettingsModal.tsx
  • src/app/src/pages/redteam/setup/components/Purpose.tsx
  • src/app/src/contexts/ApiHealthContext.test.tsx
  • src/app/src/pages/redteam/setup/components/Purpose.test.tsx
  • src/app/src/components/ApiSettingsModal.test.tsx
  • src/app/src/pages/launcher/page.tsx
  • src/app/src/contexts/ApiHealthContext.tsx
  • src/app/src/pages/launcher/page.test.tsx
  • src/app/src/pages/redteam/setup/components/Plugins.tsx
  • src/app/src/pages/redteam/setup/components/Targets/CustomPoliciesSection.tsx
  • src/app/src/pages/redteam/setup/components/Review.test.tsx
  • src/app/src/hooks/usePolling.ts
  • src/app/src/pages/redteam/setup/components/Review.tsx
  • src/app/src/pages/redteam/setup/components/PluginsTab.tsx
src/app/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (src/app/CLAUDE.md)

src/app/src/**/*.{ts,tsx}: Never use fetch() directly; always use callApi() from @app/utils/api for all HTTP requests
Access Zustand state outside React components via store.getState(); do not call hooks outside components
Use the @app/* path alias for internal imports as configured in Vite

Files:

  • src/app/src/App.tsx
  • src/app/src/components/ApiSettingsModal.tsx
  • src/app/src/pages/redteam/setup/components/Purpose.tsx
  • src/app/src/contexts/ApiHealthContext.test.tsx
  • src/app/src/pages/redteam/setup/components/Purpose.test.tsx
  • src/app/src/components/ApiSettingsModal.test.tsx
  • src/app/src/pages/launcher/page.tsx
  • src/app/src/contexts/ApiHealthContext.tsx
  • src/app/src/pages/launcher/page.test.tsx
  • src/app/src/pages/redteam/setup/components/Plugins.tsx
  • src/app/src/pages/redteam/setup/components/Targets/CustomPoliciesSection.tsx
  • src/app/src/pages/redteam/setup/components/Review.test.tsx
  • src/app/src/hooks/usePolling.ts
  • src/app/src/pages/redteam/setup/components/Review.tsx
  • src/app/src/pages/redteam/setup/components/PluginsTab.tsx
**/*.{tsx,jsx}

📄 CodeRabbit inference engine (.cursor/rules/react-components.mdc)

**/*.{tsx,jsx}: Use icons from @mui/icons-material
Prefer commonly used icons from @mui/icons-material for intuitive experience

Files:

  • src/app/src/App.tsx
  • src/app/src/components/ApiSettingsModal.tsx
  • src/app/src/pages/redteam/setup/components/Purpose.tsx
  • src/app/src/contexts/ApiHealthContext.test.tsx
  • src/app/src/pages/redteam/setup/components/Purpose.test.tsx
  • src/app/src/components/ApiSettingsModal.test.tsx
  • src/app/src/pages/launcher/page.tsx
  • src/app/src/contexts/ApiHealthContext.tsx
  • src/app/src/pages/launcher/page.test.tsx
  • src/app/src/pages/redteam/setup/components/Plugins.tsx
  • src/app/src/pages/redteam/setup/components/Targets/CustomPoliciesSection.tsx
  • src/app/src/pages/redteam/setup/components/Review.test.tsx
  • src/app/src/pages/redteam/setup/components/Review.tsx
  • src/app/src/pages/redteam/setup/components/PluginsTab.tsx
src/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

src/app/**/*.{ts,tsx}: In the React app (src/app), always use callApi from @app/utils/api for API calls instead of fetch()
React hooks: use useMemo for computed values and useCallback for functions that accept arguments

Files:

  • src/app/src/App.tsx
  • src/app/src/components/ApiSettingsModal.tsx
  • src/app/src/pages/redteam/setup/components/Purpose.tsx
  • src/app/src/contexts/ApiHealthContext.test.tsx
  • src/app/src/pages/redteam/setup/components/Purpose.test.tsx
  • src/app/src/components/ApiSettingsModal.test.tsx
  • src/app/src/pages/launcher/page.tsx
  • src/app/src/contexts/ApiHealthContext.tsx
  • src/app/src/pages/launcher/page.test.tsx
  • src/app/src/pages/redteam/setup/components/Plugins.tsx
  • src/app/src/pages/redteam/setup/components/Targets/CustomPoliciesSection.tsx
  • src/app/src/pages/redteam/setup/components/Review.test.tsx
  • src/app/src/hooks/usePolling.ts
  • src/app/src/pages/redteam/setup/components/Review.tsx
  • src/app/src/pages/redteam/setup/components/PluginsTab.tsx
src/app/src/components/**/*.tsx

📄 CodeRabbit inference engine (src/app/CLAUDE.md)

src/app/src/components/**/*.tsx: Create MUI styled components using @mui/material/styles styled API; use shouldForwardProp when adding custom props
Define strict TypeScript interfaces for all component props and provide defaults for optional props where appropriate
Add a displayName to components wrapped with React.memo for better debugging

Files:

  • src/app/src/components/ApiSettingsModal.tsx
  • src/app/src/components/ApiSettingsModal.test.tsx
src/app/src/{components,pages}/**/*.tsx

📄 CodeRabbit inference engine (src/app/CLAUDE.md)

src/app/src/{components,pages}/**/*.tsx: Use the class-based ErrorBoundary component (@app/components/ErrorBoundary) to wrap error-prone UI
Access theme via useTheme() from @mui/material/styles instead of hardcoding theme values
Use useMemo/useCallback only when profiling indicates benefit; avoid unnecessary memoization
Implement explicit loading and error states for components performing async operations
Prefer MUI composition and the sx prop for styling over ad-hoc inline styles

Files:

  • src/app/src/components/ApiSettingsModal.tsx
  • src/app/src/pages/redteam/setup/components/Purpose.tsx
  • src/app/src/pages/redteam/setup/components/Purpose.test.tsx
  • src/app/src/components/ApiSettingsModal.test.tsx
  • src/app/src/pages/launcher/page.tsx
  • src/app/src/pages/launcher/page.test.tsx
  • src/app/src/pages/redteam/setup/components/Plugins.tsx
  • src/app/src/pages/redteam/setup/components/Targets/CustomPoliciesSection.tsx
  • src/app/src/pages/redteam/setup/components/Review.test.tsx
  • src/app/src/pages/redteam/setup/components/Review.tsx
  • src/app/src/pages/redteam/setup/components/PluginsTab.tsx
**/*.{test,spec}.{js,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/gh-cli-workflow.mdc)

Avoid disabling or skipping tests unless absolutely necessary and documented

Files:

  • src/app/src/contexts/ApiHealthContext.test.tsx
  • src/app/src/pages/redteam/setup/components/Purpose.test.tsx
  • src/app/src/components/ApiSettingsModal.test.tsx
  • src/app/src/pages/launcher/page.test.tsx
  • src/app/src/pages/redteam/setup/components/Review.test.tsx
src/app/src/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (src/app/CLAUDE.md)

src/app/src/**/*.test.{ts,tsx}: In tests, import testing APIs from vitest (e.g., describe, it, expect, vi) and not from @jest/globals
Mock API calls in tests; do not make real network requests (e.g., vi.mock('@app/utils/api'))
Wrap MUI components in a ThemeProvider when testing components that depend on theme
Prefer Testing Library queries that reflect user behavior (getByRole, getByLabelText, etc.)
Clean up after tests as needed (e.g., clear/reset mocks) despite Vitest auto-cleanup
When testing React Router components, render under a MemoryRouter
Use Vitest as the test runner for the frontend app; do not use Jest APIs

Files:

  • src/app/src/contexts/ApiHealthContext.test.tsx
  • src/app/src/pages/redteam/setup/components/Purpose.test.tsx
  • src/app/src/components/ApiSettingsModal.test.tsx
  • src/app/src/pages/launcher/page.test.tsx
  • src/app/src/pages/redteam/setup/components/Review.test.tsx
src/app/src/hooks/**/*.{ts,tsx}

📄 CodeRabbit inference engine (src/app/CLAUDE.md)

Custom hooks that read context must validate the context and throw a clear error if undefined

Files:

  • src/app/src/hooks/usePolling.ts
🧠 Learnings (8)
📚 Learning: 2025-10-11T15:29:11.784Z
Learnt from: CR
PR: promptfoo/promptfoo#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-11T15:29:11.784Z
Learning: Applies to src/app/**/*.{ts,tsx} : React hooks: use useMemo for computed values and useCallback for functions that accept arguments

Applied to files:

  • src/app/src/pages/redteam/setup/components/Purpose.tsx
  • src/app/src/pages/redteam/setup/components/PluginsTab.tsx
📚 Learning: 2025-10-05T17:00:56.424Z
Learnt from: CR
PR: promptfoo/promptfoo#0
File: test/CLAUDE.md:0-0
Timestamp: 2025-10-05T17:00:56.424Z
Learning: Applies to test/providers/**/*.test.ts : Provider tests must cover: success case, error cases (4xx, 5xx, rate limits), configuration validation, and token usage tracking

Applied to files:

  • src/app/src/contexts/ApiHealthContext.test.tsx
📚 Learning: 2025-10-05T16:58:47.598Z
Learnt from: CR
PR: promptfoo/promptfoo#0
File: src/providers/CLAUDE.md:0-0
Timestamp: 2025-10-05T16:58:47.598Z
Learning: Applies to src/providers/test/providers/**/*.test.{ts,tsx,js,jsx} : Provider tests must cover rate limits, timeouts, and invalid configuration scenarios

Applied to files:

  • src/app/src/contexts/ApiHealthContext.test.tsx
📚 Learning: 2025-10-05T16:58:47.598Z
Learnt from: CR
PR: promptfoo/promptfoo#0
File: src/providers/CLAUDE.md:0-0
Timestamp: 2025-10-05T16:58:47.598Z
Learning: Applies to src/providers/test/providers/**/*.test.{ts,tsx,js,jsx} : Provider tests must mock API responses and never call real external services

Applied to files:

  • src/app/src/contexts/ApiHealthContext.test.tsx
📚 Learning: 2025-07-18T17:26:23.087Z
Learnt from: CR
PR: promptfoo/promptfoo#0
File: .cursor/rules/jest.mdc:0-0
Timestamp: 2025-07-18T17:26:23.087Z
Learning: Applies to test/**/*.{test,spec}.ts : Mock API responses to avoid external dependencies in tests

Applied to files:

  • src/app/src/pages/redteam/setup/components/Purpose.test.tsx
  • src/app/src/components/ApiSettingsModal.test.tsx
📚 Learning: 2025-10-05T16:56:39.114Z
Learnt from: CR
PR: promptfoo/promptfoo#0
File: src/app/CLAUDE.md:0-0
Timestamp: 2025-10-05T16:56:39.114Z
Learning: Applies to src/app/src/**/*.test.{ts,tsx} : Mock API calls in tests; do not make real network requests (e.g., vi.mock('app/utils/api'))

Applied to files:

  • src/app/src/pages/redteam/setup/components/Purpose.test.tsx
  • src/app/src/components/ApiSettingsModal.test.tsx
  • src/app/src/pages/launcher/page.test.tsx
  • src/app/src/pages/redteam/setup/components/Review.test.tsx
📚 Learning: 2025-10-05T16:56:39.114Z
Learnt from: CR
PR: promptfoo/promptfoo#0
File: src/app/CLAUDE.md:0-0
Timestamp: 2025-10-05T16:56:39.114Z
Learning: Applies to src/app/src/**/*.test.{ts,tsx} : In tests, import testing APIs from vitest (e.g., describe, it, expect, vi) and not from jest/globals

Applied to files:

  • src/app/src/components/ApiSettingsModal.test.tsx
  • src/app/src/pages/launcher/page.test.tsx
📚 Learning: 2025-10-05T17:00:56.424Z
Learnt from: CR
PR: promptfoo/promptfoo#0
File: test/CLAUDE.md:0-0
Timestamp: 2025-10-05T17:00:56.424Z
Learning: Applies to test/**/*.test.ts : Use Jest (not Vitest) APIs in this suite; avoid importing vitest

Applied to files:

  • src/app/src/pages/launcher/page.test.tsx
🧬 Code graph analysis (7)
src/app/src/App.tsx (1)
src/app/src/contexts/ApiHealthContext.tsx (1)
  • ApiHealthProvider (33-84)
src/app/src/contexts/ApiHealthContext.test.tsx (2)
src/app/src/contexts/ApiHealthContext.tsx (2)
  • useApiHealth (25-31)
  • ApiHealthProvider (33-84)
src/app/src/hooks/usePolling.ts (1)
  • usePolling (3-8)
src/app/src/contexts/ApiHealthContext.tsx (1)
src/app/src/hooks/usePolling.ts (1)
  • usePolling (3-8)
src/app/src/pages/redteam/setup/components/Plugins.tsx (1)
src/app/src/contexts/ApiHealthContext.tsx (1)
  • useApiHealth (25-31)
src/app/src/pages/redteam/setup/components/Targets/CustomPoliciesSection.tsx (1)
src/app/src/contexts/ApiHealthContext.tsx (1)
  • useApiHealth (25-31)
src/app/src/pages/redteam/setup/components/Review.tsx (1)
src/app/src/contexts/ApiHealthContext.tsx (1)
  • useApiHealth (25-31)
src/app/src/pages/redteam/setup/components/PluginsTab.tsx (1)
src/app/src/contexts/ApiHealthContext.tsx (1)
  • useApiHealth (25-31)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (18)
  • GitHub Check: Tusk Test Runner (4)
  • GitHub Check: Tusk Test Runner (2)
  • GitHub Check: Tusk Test Runner (1)
  • GitHub Check: Tusk Test Runner (3)
  • GitHub Check: Redteam (Staging API)
  • GitHub Check: Test on Node 22.x and windows-latest
  • GitHub Check: Test on Node 24.x and windows-latest
  • GitHub Check: Share Test
  • GitHub Check: Build Docs
  • GitHub Check: webui tests
  • GitHub Check: Style Check
  • GitHub Check: Test on Node 24.x and ubuntu-latest
  • GitHub Check: Test on Node 20.x and macOS-latest
  • GitHub Check: Redteam (Production API)
  • GitHub Check: Test on Node 20.x and windows-latest
  • GitHub Check: Test on Node 20.x and ubuntu-latest
  • GitHub Check: Build on Node 24.x
  • GitHub Check: Tusk Tester
🔇 Additional comments (17)
src/app/src/pages/redteam/setup/components/Purpose.tsx (2)

4-4: LGTM - Import path updated correctly.

The import path has been updated to use the new context-based API health system.


276-280: The on-demand health check is justified. Verification confirms the ApiHealthProvider uses usePolling with a 3000ms (3-second) interval. The checkHealth() call in Purpose.tsx is complementary, not redundant—it provides immediate feedback when the user configures a target, while the background polling continues for ongoing monitoring. The dependency array [hasTargetConfigured, checkHealth] is properly configured to prevent infinite loops. This is sound UX design: instant user feedback on action + continuous monitoring.

src/app/src/pages/redteam/setup/components/Plugins.tsx (2)

3-3: LGTM - Import path migrated to context.

The import has been correctly updated to use the new ApiHealthContext.


141-141: LGTM - Simplified to use context-provided status.

The component now relies on the ApiHealthProvider for automatic polling instead of triggering health checks on mount. This reduces redundant API calls.

src/app/src/components/ApiSettingsModal.tsx (2)

3-3: LGTM - Import updated with type export.

The import correctly uses the new context path and includes the ApiHealthStatus type.


48-52: LGTM - Intentional health check on modal open.

Calling checkHealth() when the settings modal opens provides immediate, up-to-date status information to the user. This is appropriate for a settings UI.

src/app/src/pages/launcher/page.test.tsx (1)

7-7: LGTM - Test imports and mocks updated correctly.

The test has been updated to import and mock the new ApiHealthContext path. The mock structure remains compatible with the existing tests.

Also applies to: 45-52

src/app/src/App.tsx (1)

27-27: LGTM - Context provider correctly integrated.

The ApiHealthProvider is properly wrapped around the RouterProvider, making API health status available to all routes. This establishes the context boundary for the entire application.

Also applies to: 89-91

src/app/src/pages/launcher/page.tsx (1)

19-19: LGTM - Import path updated.

src/app/src/pages/redteam/setup/components/PluginsTab.tsx (2)

1-1: LGTM - Clean migration to context-based health status.

The component correctly removes useEffect from imports and updates to use the ApiHealthContext. It now relies solely on the context-provided status without triggering its own health checks.

Also applies to: 55-55


110-110: LGTM - Simplified to consume status from context.

The component no longer calls checkHealth() on mount, relying on the ApiHealthProvider's automatic polling instead. This eliminates redundant API calls.

src/app/src/components/ApiSettingsModal.test.tsx (2)

1-1: LGTM - Test updated to mock new context path.

The test correctly imports and mocks the ApiHealthContext module path.

Also applies to: 7-9


26-26: Message type is string, not null | string—test changes are correct.

The verification confirms that ApiHealthContext defines message as type string with a default value of '' (empty string), not null. The test's use of message: '' is correct and consistent with the actual implementation.

Likely an incorrect or invalid review comment.

src/app/src/pages/redteam/setup/components/Targets/CustomPoliciesSection.tsx (1)

3-3: Import swap to context looks good

Switching to @app/contexts/ApiHealthContext aligns with the new provider pattern. No issues.

src/app/src/pages/redteam/setup/components/Purpose.test.tsx (1)

31-33: Mock path update is correct

Mocking useApiHealth from @app/contexts/ApiHealthContext aligns with the refactor. Matches testing guidelines to mock API/state sources.

src/app/src/pages/redteam/setup/components/Review.tsx (1)

4-4: Import swap to context is correct

Matches the new centralized provider approach.

src/app/src/pages/redteam/setup/components/Review.test.tsx (1)

6-6: LGTM! Clean migration to context-based API health.

The test file has been correctly updated to use the new ApiHealthContext module instead of the old hook. The mock structure is preserved, and the test descriptions now accurately reflect that the component reads from context rather than performing mount-time health checks.

Also applies to: 47-49, 305-320

Comment on lines 25 to 31
export const useApiHealth = () => {
// Ensure that the context is initialized
if (!ApiHealthContext) {
throw new Error('useApiHealth must be used within an ApiHealthProvider');
}
return useContext(ApiHealthContext);
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix the context validation check.

The condition if (!ApiHealthContext) will always be false because createContext() returns an object, which is always truthy. This check doesn't actually validate whether the hook is used within a provider.

If you want to enforce provider usage, you could check the context value instead, but React's default behavior (returning the default context value) is typically sufficient. Consider removing this check or using a sentinel value in the default context to detect missing providers.

Apply this diff to remove the ineffective check:

 export const useApiHealth = () => {
-  // Ensure that the context is initialized
-  if (!ApiHealthContext) {
-    throw new Error('useApiHealth must be used within an ApiHealthProvider');
-  }
   return useContext(ApiHealthContext);
 };
🤖 Prompt for AI Agents
In src/app/src/contexts/ApiHealthContext.tsx around lines 25 to 31, the current
validation uses `if (!ApiHealthContext)` which is ineffective because
createContext returns a truthy object; remove that conditional check and simply
return `useContext(ApiHealthContext)` (or, if you want provider enforcement,
change the default context value to a sentinel like `null` and type the context
accordingly so you can check the returned value for null and throw a clear
error).

@use-tusk
Copy link
Contributor

use-tusk bot commented Oct 20, 2025

⏩ PR identified as a refactor (6f48d4d) View output ↗


View check history

Commit Status Output Created (UTC)
0f207e6 ⏩ Skipped due to new commit on branch Output Oct 20, 2025 5:12PM
d9f7e85 ⏩ No tests generated Output Oct 20, 2025 5:14PM
c9287e3 ❌ Generated 21 tests - 16 passed, 5 failed Tests Oct 20, 2025 5:40PM
9a37c8e ⏩ PR identified as a refactor Output Oct 21, 2025 2:50PM
b987bcf ⏩ Skipped due to new commit on branch Output Oct 21, 2025 3:13PM
25a7719 ⏩ Skipped due to new commit on branch Output Oct 21, 2025 3:21PM
1618751 ⏩ Skipped due to new commit on branch Output Oct 21, 2025 3:27PM
2aba89c ⏩ Test environment setup failed Output Oct 21, 2025 3:28PM
448972b ⏩ Skipped due to new commit on branch Output Oct 21, 2025 3:33PM
960cac5 ⏩ Skipped due to new commit on branch Output Oct 21, 2025 3:40PM
4e09126 ⏩ Test environment setup failed Output Oct 21, 2025 4:16PM
d3dc354 ⏩ Skipped due to new commit on branch Output Oct 21, 2025 4:25PM
0e85a90 ⏩ Test environment setup failed Output Oct 21, 2025 4:47PM
73f20d0 ⏩ Skipped due to new commit on branch Output Oct 21, 2025 4:54PM
19de19d ⏩ Skipped due to new commit on branch Output Oct 21, 2025 5:13PM
7e0e7b7 ⏩ Test environment setup failed Output Oct 21, 2025 5:35PM
2bfbea4 🔄 Running Tusk Tester Output Oct 21, 2025 9:53PM
02d937d ⏩ No tests generated Output Oct 22, 2025 5:54AM
2fdd89c ⏩ Skipped due to new commit on branch Output Oct 22, 2025 7:55PM
1c5f66d ⏩ Skipped due to new commit on branch Output Oct 22, 2025 8:10PM
a1893d4 ⏩ No tests generated Output Oct 22, 2025 9:04PM
eff264c ⏩ PR identified as a refactor Output Oct 22, 2025 9:19PM
26ae465 ⏩ Skipped due to new commit on branch Output Oct 22, 2025 9:26PM
6f48d4d ⏩ PR identified as a refactor Output Oct 22, 2025 9:35PM

View output in GitHub ↗

mldangelo
mldangelo previously approved these changes Oct 20, 2025
Copy link
Member

@mldangelo mldangelo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@mldangelo
Copy link
Member

Please consider adding the tusk tests!

Copy link
Contributor

@sklein12 sklein12 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could use react query to do this instead of re-implementing the logic ourselves.

export function useApiHealth() {
  return useQuery({
    queryKey: ['apiHealth'],
    queryFn: async () => {
      const response = await callApi('/remote-health', {
        cache: 'no-store',
      });
      const data = await response.json() as { status: string; message: string };
      
      let status: ApiHealthStatus;
      if (data.status === 'DISABLED') {
        status = 'disabled';
      } else {
        status = data.status === 'OK' ? 'connected' : 'blocked';
      }
      
      return { status, message: data.message };
    },
    refetchInterval: 3000, // Poll every 3 seconds
    retry: false,
    staleTime: 2000,
  });
}

@mldangelo
Copy link
Member

@sklein12 react-query is great idea for the future. This is an improvement that could land today as is.

@sklein12
Copy link
Contributor

sklein12 commented Oct 21, 2025

React query is a 10 line drop in, it's a 10 minute refactor. I think it's worth it. This change is super complex and a lot of code to handle a single http call. This is the same thing we talked about two weeks ago with the other PR.

@will-holley will-holley changed the title refactor(app): API Health Context refactor(app): Reduce unnecessary API health requests Oct 21, 2025
@will-holley will-holley merged commit eb8d7af into main Oct 23, 2025
35 checks passed
@will-holley will-holley deleted the refactor/api-health-context branch October 23, 2025 13:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants