Skip to content

feat(hook): Add frontend feature control and admin hook page#9575

Open
Bo-Onyx wants to merge 3 commits intomainfrom
bo/hook_ui
Open

feat(hook): Add frontend feature control and admin hook page#9575
Bo-Onyx wants to merge 3 commits intomainfrom
bo/hook_ui

Conversation

@Bo-Onyx
Copy link
Contributor

@Bo-Onyx Bo-Onyx commented Mar 23, 2026

Description

Summary

  • Settings page (/admin/hooks) — lists all registered hook points (fetched from /api/admin/hooks/specs) with search, per-hook icons, description, and a docs link. Connect button is a placeholder for the create/edit flow (next PR).
  • Feature gate — page redirects to / if hooks are not enabled. Uses hooks_enabled from the existing /api/settings response (via useSettingsContext) rather than a dedicated endpoint — no extra network request.
  • Backend — exposes hooks_enabled: bool in UserSettings (populated from HOOKS_AVAILABLE, which checks both HOOK_ENABLED and single-tenant mode). Adds placeholder docs_url to DocumentIngestionSpec and QueryProcessingSpec.
  • Sidebar — "Hook Extensions" entry added under Integrations, visible only when hooks_enabled.
  • New icons — SvgHookNodes (sidebar/page header) and SvgFileBroadcast (document ingestion hook point), added to the Opal icon registry in alphabetical order.
  • InputSearch component — new refresh-components input with subtle border-on-hover style, used on the hooks page. Includes Storybook stories.
  • TypeScript interfaces — interfaces.ts mirrors all backend Pydantic models for hooks (HookResponse, HookCreateRequest, HookUpdateRequest, HookExecutionRecord, HookValidateResponse, etc.) for use in the next PR.

Not in this PR

  • Create/edit/delete hook modal (Connect button is inert)
  • Activate/deactivate, validate, and execution log UI

Motivation: We want to support customer to inject function into certain point in our pipeline.
Eng Doc: https://docs.google.com/document/d/1wCQ4jcuscDLBIuVwzG8yT6UnHVWgIi5gdteOhe1SqhU/edit?tab=t.0
Linear: https://linear.app/onyx-app/project/hooks-14fc5597dc91/overview
UI mocks:
https://www.figma.com/design/sNcHyrXBLtTFDK0ijjMnSk/Admin-Panel?node-id=4756-763808&p=f&m=dev

How Has This Been Tested?

Checked on UI

hook not enabled
Screenshot 2026-03-23 at 2 51 40 PM

hook enabled
Screenshot 2026-03-23 at 5 20 38 PM

Screenshot 2026-03-23 at 5 21 05 PM

Additional Options

  • [Optional] Please cherry-pick this PR to the latest release version.
  • [Optional] Override Linear Check

Summary by cubic

Adds an admin Hook Extensions page behind a feature flag. The page shows only when hooks are available (single-tenant + HOOK_ENABLED=true) and lists hook points with search and docs links.

  • New Features

    • Backend: exposes UserSettings.hooks_enabled (via HOOKS_AVAILABLE) in /api/settings; adds docs_url to document_ingestion and query_processing specs.
    • Frontend: adds SvgHookNodes and SvgFileBroadcast to @opal/icons; registers ADMIN_ROUTES.HOOKS at /admin/hooks; shows “Hook Extensions” under Integrations only when settings.hooks_enabled; guards the page with a toast + redirect if disabled; updates the Settings interface with hooks_enabled.
    • Hooks page: fetches /api/admin/hooks/specs via useHookSpecs; shows searchable cards with per-hook icons, a placeholder “Connect” button, and optional docs link; adds a reusable InputSearch with Storybook; adds TS interfaces for hook models.
  • Migration

    • To expose the page, run single-tenant and set HOOK_ENABLED=true.

Written for commit 75c960c. Summary will update on new commits.

@Bo-Onyx Bo-Onyx requested a review from a team as a code owner March 23, 2026 21:38
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 23, 2026

Greptile Summary

This PR adds a frontend admin Hook Extensions page (/admin/hooks) behind a feature gate, exposing hooks_enabled via the existing /api/settings endpoint. It also introduces two new Opal icons, a reusable InputSearch component, TypeScript interface scaffolding for the upcoming Connect flow, and a conditional sidebar entry under Integrations.

Key changes and findings:

  • Backend (settings/api.py, settings/models.py): hooks_enabled is correctly derived from the module-level HOOKS_AVAILABLE constant and surfaced in UserSettings. Defaults to false, so no regressions for deployments without HOOK_ENABLED=true.
  • Feature gate (HooksPage/index.tsx): Reads hooks_enabled from useSettingsContext and redirects to / with toast.info when disabled. Guard logic prevents content flash before redirect.
  • Hook layer coupling (useHookSpecs.ts): The shared hook at web/src/hooks/useHookSpecs.ts imports HookPointMeta from @/refresh-pages/admin/HooksPage/interfaces — a page-specific directory. Per AGENTS.md §3, API response shapes should live in a shared location (e.g. web/src/interfaces/hooks.ts) so a generic hook does not depend on a page's private types.
  • InputSearch component is well-structured; all Storybook stories use named function syntax per AGENTS.md §2.
  • Icon barrel exports are in correct alphabetical order after this PR.

Confidence Score: 4/5

  • Safe to merge — no runtime errors, security issues, or regressions; one minor layer-coupling style issue in useHookSpecs.ts.
  • The feature is gated behind a flag that defaults to false, so there is no risk to existing deployments. The backend changes are additive and minimal. The main concern is a style/architecture issue — useHookSpecs.ts importing from a page-specific interfaces.ts — which does not affect runtime behaviour but violates AGENTS.md §3 and will become a friction point when the hook is reused in the upcoming Connect modal PR.
  • web/src/hooks/useHookSpecs.ts — type import from page-specific location should be moved to a shared interfaces file before the next PR adds more consumers of this hook.

Important Files Changed

Filename Overview
backend/onyx/server/settings/api.py Adds hooks_enabled=HOOKS_AVAILABLE to the UserSettings response. HOOKS_AVAILABLE is a module-level constant evaluated at startup from the env var — correct approach for a feature flag.
web/src/hooks/useHookSpecs.ts SWR hook for /api/admin/hooks/specs. Implementation is clean, but imports HookPointMeta from a page-specific directory (@/refresh-pages/admin/HooksPage/interfaces), inverting the natural layer dependency — a shared hook should not depend on a page's private types.
web/src/refresh-pages/admin/HooksPage/HooksContent.tsx Main hooks listing UI. Uses useHookSpecs correctly, handles loading/error/empty states, and renders hook cards with optional docs links. Several previously-flagged issues (empty state, error state, toast redundancy, raw <a> tag) are addressed or acknowledged.
web/src/refresh-pages/admin/HooksPage/index.tsx Feature-gated page wrapper. Reads hooks_enabled from settings context and redirects to / with a toast.info when disabled. Guard logic is correct — shows a loader while loading or while not yet enabled to prevent content flash before redirect.
web/src/refresh-components/inputs/InputSearch.tsx New reusable InputSearch component built on InputTypeIn. Correctly omits variant/leftSearchIcon from the public API and derives them from disabled. The subtle border-on-hover styling follows the spec.

Sequence Diagram

sequenceDiagram
    participant Browser
    participant HooksPage as HooksPage (index.tsx)
    participant SettingsCtx as useSettingsContext
    participant HooksContent as HooksContent.tsx
    participant useHookSpecs
    participant API as /api/admin/hooks/specs
    participant SettingsAPI as /api/settings

    Browser->>HooksPage: Navigate to /admin/hooks
    HooksPage->>SettingsCtx: read { settings, settingsLoading }
    SettingsCtx-->>HooksPage: settingsLoading=true → render SimpleLoader

    SettingsCtx->>SettingsAPI: GET /api/settings
    SettingsAPI-->>SettingsCtx: { hooks_enabled: bool, ... }

    alt hooks_enabled = false
        HooksPage->>Browser: toast.info + router.replace("/")
    else hooks_enabled = true
        HooksPage->>HooksContent: render
        HooksContent->>useHookSpecs: call hook
        useHookSpecs->>API: SWR GET /api/admin/hooks/specs
        API-->>useHookSpecs: HookPointMeta[]
        useHookSpecs-->>HooksContent: { specs, isLoading, error }
        HooksContent-->>Browser: render hook cards with search + Connect button
    end
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: web/src/hooks/useHookSpecs.ts
Line: 5

Comment:
**Hook imports API type from page-specific location**

`useHookSpecs` lives in `web/src/hooks/` — a shared layer — but imports `HookPointMeta` from `@/refresh-pages/admin/HooksPage/interfaces`, which is a page-specific directory. This inverts the natural dependency direction: a generic, reusable hook should not depend on a concrete page's types.

Per AGENTS.md §3, API response shapes ("shared models, API response shapes, enums, etc.") belong in a co-located `interfaces.ts` or in the shared `web/src/interfaces/` directory. Moving `HookPointMeta` (and the other hook API types) there would also make it easier to reuse the hook in a second page (e.g., the upcoming Connect modal) without importing from the first page.

```suggestion
import { HookPointMeta } from "@/interfaces/hooks";
```

Then move `HookPointMeta` (and the rest of the hook-related API types currently in `HooksPage/interfaces.ts`) to `web/src/interfaces/hooks.ts`, and update the import in `HooksContent.tsx` accordingly.

**Rule Used:** For frontend changes (changes that touch the /web ... ([source](https://app.greptile.com/review/custom-context?memory=auto-aaef32fd))

How can I resolve this? If you propose a fix, please make it concise.

Reviews (11): Last reviewed commit: "address comments" | Re-trigger Greptile

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

2 issues found across 10 files

Confidence score: 4/5

  • This PR is likely safe to merge: both reported issues are low severity (4/10) and appear to be localized UX/observability gaps rather than functional breakages.
  • The most impactful issue is in web/src/app/admin/hooks/HooksPageContent.tsx: when filtered is empty, users can see a blank area with no empty-state message, which can be confusing during search or when no hooks exist.
  • Missing error logging in web/src/app/admin/hooks/HooksPageContent.tsx reduces debuggability of SWR fetch failures, but does not by itself indicate a regression in core behavior.
  • Pay close attention to web/src/app/admin/hooks/HooksPageContent.tsx - add explicit empty-state rendering and log fetch errors before showing the error callout.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="web/src/app/admin/hooks/HooksPageContent.tsx">

<violation number="1" location="web/src/app/admin/hooks/HooksPageContent.tsx:46">
P2: Log the SWR fetch error before rendering the error callout so failures are traceable during debugging.

(Based on your team's feedback about always logging underlying errors in error-handling paths.) [FEEDBACK_USED]</violation>

<violation number="2" location="web/src/app/admin/hooks/HooksPageContent.tsx:70">
P2: No empty-state rendered when `filtered` is empty — the user sees a blank area below the search box with no indication whether their search matched nothing or no hooks exist. Add a conditional branch before the `.map()` to show an appropriate message.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 23, 2026

Preview Deployment

Status Preview Commit Updated
https://onyx-preview-et1cqm09m-danswer.vercel.app 75c960c 2026-03-24 19:03:27 UTC

@github-actions
Copy link
Contributor

github-actions bot commented Mar 23, 2026

🖼️ Visual Regression Report

Project Changed Added Removed Unchanged Report
admin 8 0 0 116 View Report
exclusive 0 0 0 8 ✅ No changes

Copy link
Contributor

@nmgarza5 nmgarza5 left a comment

Choose a reason for hiding this comment

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

some comments that need to be addressed but approving to unblock

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.

2 participants