Repository navigation
feat: add URL connector #2426
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
lucaseduoli
wants to merge
40
commits into
main
Choose a base branch
from
feat/url_ingest_connector
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+10,953
−2,396
Open
feat: add URL connector #2426
Changes from all commits
Commits
Show all changes
40 commits
Select commit
Hold shift + click to select a range
50a188b
v1 of URL website crawler connector
lucaseduoli 34f6f43
Use scrapy to scrape websites, dont persist failures
lucaseduoli 87d053a
Merge remote-tracking branch 'origin/main' into feat/url_ingest_conne…
lucaseduoli c5aa88f
add feature flag to disable url functionality
lucaseduoli f1cbf28
style: ruff autofix (auto)
autofix-ci[bot] b9a6c26
fixed coderabbit picks
lucaseduoli 5360980
style: ruff autofix (auto)
autofix-ci[bot] 6aaed57
fixed react doctor
lucaseduoli de0be15
refactor code in smaller components
lucaseduoli 88aafe5
Add more tests to increase coverage, fix use website pages table
lucaseduoli fee8afd
fix coderabbit pick
lucaseduoli 5d0f2a1
added change detection functionality
lucaseduoli 66f46c3
style: ruff autofix (auto)
autofix-ci[bot] 464c9a9
test website crawl run and change processor
lucaseduoli f3db313
fixed frontend to use react query instead of fetch in usecallbacks
lucaseduoli 3078768
Delete URL_CONNECTOR_PLAN.md
lucaseduoli 51a2a51
Fix Edwin picks
lucaseduoli 4aaec60
style: ruff autofix (auto)
autofix-ci[bot] 05e6fa4
fixed regression
lucaseduoli 3575936
style: ruff autofix (auto)
autofix-ci[bot] 33da483
fixed race condition
lucaseduoli abfd608
added source operation locks
lucaseduoli 37476c5
style: ruff autofix (auto)
autofix-ci[bot] 47e9580
remove sqlite saved url ingestions in favor of only storing on opense…
lucaseduoli fba2c67
fix Edwin picks
lucaseduoli ca8c96c
disabled pages goes to the end, not allow same name connections
lucaseduoli 58fc0fd
adjust design of non-ibm modal
lucaseduoli d6da4c7
Merge remote-tracking branch 'origin/main' into feat/url_ingest_conne…
lucaseduoli 183da0d
Merge remote-tracking branch 'origin/main' into feat/url_ingest_conne…
lucaseduoli e53b8a9
fix picks
lucaseduoli 1228b74
Merge remote-tracking branch 'origin/main' into feat/url_ingest_conne…
lucaseduoli c7f87c8
fix types
lucaseduoli b201de7
fix picks
lucaseduoli a8c9ea6
add e2e tests
lucaseduoli caa49d9
Merge remote-tracking branch 'origin/main' into feat/url_ingest_conne…
lucaseduoli e04c67f
open url modal as state not route
lucaseduoli 7d39af5
fix crawl limits and chunk search behaviors
lucaseduoli aba707e
fixed mike picks
lucaseduoli 6fb9364
fix p1
lucaseduoli b349af0
Merge remote-tracking branch 'origin/main' into feat/url_ingest_conne…
lucaseduoli File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
50 changes: 50 additions & 0 deletions
50
frontend/app/api/mutations/useWebsiteSourceMutation.test.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| import { act, renderHook, waitFor } from "@testing-library/react"; | ||
| import { HttpResponse, http } from "msw"; | ||
| import { describe, expect, it } from "vitest"; | ||
| import { useGetTasksQuery } from "@/app/api/queries/useGetTasksQuery"; | ||
| import { server } from "@/test-utils/msw/server"; | ||
| import { createQueryWrapper } from "@/test-utils/render"; | ||
| import { useSyncWebsiteSourceMutation } from "./useWebsiteSourceMutation"; | ||
|
|
||
| describe("useSyncWebsiteSourceMutation", () => { | ||
| it("syncs a child page and automatically refreshes active task queries", async () => { | ||
| let syncCalls = 0; | ||
| let taskRequests = 0; | ||
| server.use( | ||
| http.post( | ||
| "/api/connectors/url/sources/source-1/pages/page-1/sync", | ||
| () => { | ||
| syncCalls += 1; | ||
| return HttpResponse.json({ task_id: "task-1" }); | ||
| }, | ||
| ), | ||
| http.get("/api/tasks/enhanced", () => { | ||
| taskRequests += 1; | ||
| return HttpResponse.json({ tasks: [] }); | ||
| }), | ||
| ); | ||
|
|
||
| const { result } = renderHook( | ||
| () => { | ||
| useGetTasksQuery(); | ||
| return useSyncWebsiteSourceMutation(); | ||
| }, | ||
| { wrapper: createQueryWrapper() }, | ||
| ); | ||
|
|
||
| await waitFor(() => expect(taskRequests).toBeGreaterThan(0)); | ||
| const taskRequestsBeforeSync = taskRequests; | ||
|
|
||
| await act(async () => { | ||
| await result.current.mutateAsync({ | ||
| sourceId: "source-1", | ||
| pageId: "page-1", | ||
| }); | ||
| }); | ||
|
|
||
| expect(syncCalls).toBe(1); | ||
| await waitFor(() => | ||
| expect(taskRequests).toBeGreaterThan(taskRequestsBeforeSync), | ||
| ); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,135 @@ | ||
| import { | ||
| type QueryClient, | ||
| useMutation, | ||
| useQueryClient, | ||
| } from "@tanstack/react-query"; | ||
| import { TASKS_QUERY_KEY } from "@/app/api/queries/useGetTasksQuery"; | ||
| import { websiteSourceQueryKey } from "@/app/api/queries/useGetWebsiteSourceQuery"; | ||
| import { websiteSourcesQueryKey } from "@/app/api/queries/useGetWebsiteSourcesQuery"; | ||
| import type { WebsiteSource } from "@/components/website-pages/types"; | ||
|
|
||
| export interface CreateWebsiteSourceRequest { | ||
| name: string; | ||
| starting_url: string; | ||
| scope: "path" | "page" | "site"; | ||
| allow_subdomains: boolean; | ||
| additional_hosts: string[]; | ||
| include_paths: string[]; | ||
| exclude_paths: string[]; | ||
| max_pages: number; | ||
| max_depth: number; | ||
| max_downloaded_mb: number; | ||
| max_crawl_minutes: number; | ||
| change_detection: "normalized_content_hash" | "always_reingest"; | ||
| resync_behavior: "full" | "root"; | ||
| removed_page_behavior: "retain" | "delete"; | ||
| } | ||
|
|
||
| export interface WebsiteSourceTaskResponse { | ||
| last_task_id?: string; | ||
| task_id?: string; | ||
| } | ||
|
|
||
| export interface WebsiteSourcePageRequest { | ||
| sourceId: string; | ||
| pageId?: string; | ||
| } | ||
|
|
||
| async function readError(response: Response, fallback: string) { | ||
| const data = (await response.json().catch(() => ({}))) as { | ||
| detail?: string; | ||
| error?: string; | ||
| }; | ||
| return data.detail || data.error || fallback; | ||
| } | ||
|
|
||
| async function createWebsiteSource( | ||
| request: CreateWebsiteSourceRequest, | ||
| ): Promise<WebsiteSource & WebsiteSourceTaskResponse> { | ||
| const response = await fetch("/api/connectors/url/sources", { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify(request), | ||
| }); | ||
| if (!response.ok) { | ||
| throw new Error( | ||
| await readError(response, "Could not create website source"), | ||
| ); | ||
| } | ||
| return response.json(); | ||
| } | ||
|
|
||
| async function syncWebsiteSource({ | ||
| sourceId, | ||
| pageId, | ||
| }: WebsiteSourcePageRequest): Promise<WebsiteSourceTaskResponse> { | ||
| const endpoint = pageId | ||
| ? `/api/connectors/url/sources/${sourceId}/pages/${pageId}/sync` | ||
| : `/api/connectors/url/sources/${sourceId}/sync`; | ||
| const response = await fetch(endpoint, { method: "POST" }); | ||
| if (!response.ok) { | ||
| throw new Error(await readError(response, "Could not start website sync")); | ||
| } | ||
| return response.json(); | ||
| } | ||
|
|
||
| async function deleteWebsiteSourcePage({ | ||
| sourceId, | ||
| pageId, | ||
| }: WebsiteSourcePageRequest): Promise<void> { | ||
| const endpoint = pageId | ||
| ? `/api/connectors/url/sources/${sourceId}/pages/${pageId}` | ||
| : `/api/connectors/url/sources/${sourceId}`; | ||
| const response = await fetch(endpoint, { method: "DELETE" }); | ||
| if (!response.ok) { | ||
| throw new Error( | ||
| await readError(response, "Could not delete website source"), | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| function invalidateWebsiteSourceQueries( | ||
| queryClient: QueryClient, | ||
| sourceId: string, | ||
| ) { | ||
| queryClient.invalidateQueries({ queryKey: websiteSourceQueryKey(sourceId) }); | ||
| queryClient.invalidateQueries({ queryKey: websiteSourcesQueryKey }); | ||
| queryClient.invalidateQueries({ queryKey: ["search"] }); | ||
| queryClient.invalidateQueries({ queryKey: [...TASKS_QUERY_KEY] }); | ||
| } | ||
|
|
||
| export function useCreateWebsiteSourceMutation() { | ||
| const queryClient = useQueryClient(); | ||
|
|
||
| return useMutation({ | ||
| mutationFn: createWebsiteSource, | ||
| onSuccess: () => { | ||
| queryClient.invalidateQueries({ queryKey: ["search"] }); | ||
| queryClient.invalidateQueries({ queryKey: ["listFiles"] }); | ||
| queryClient.invalidateQueries({ queryKey: websiteSourcesQueryKey }); | ||
| queryClient.invalidateQueries({ queryKey: [...TASKS_QUERY_KEY] }); | ||
| }, | ||
| }); | ||
| } | ||
|
|
||
| export function useSyncWebsiteSourceMutation() { | ||
| const queryClient = useQueryClient(); | ||
|
|
||
| return useMutation({ | ||
| mutationFn: syncWebsiteSource, | ||
| onSuccess: (_data, { sourceId }) => { | ||
| invalidateWebsiteSourceQueries(queryClient, sourceId); | ||
| }, | ||
| }); | ||
| } | ||
|
|
||
| export function useDeleteWebsiteSourcePageMutation() { | ||
| const queryClient = useQueryClient(); | ||
|
|
||
| return useMutation({ | ||
| mutationFn: deleteWebsiteSourcePage, | ||
| onSuccess: (_data, { sourceId }) => { | ||
| invalidateWebsiteSourceQueries(queryClient, sourceId); | ||
| }, | ||
| }); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Invalidate
listFilesafter a website sync or delete.invalidateWebsiteSourceQueriesinvalidates the source, search, and task queries. It does not invalidate["listFiles"]. The Knowledge dropdown callsdeleteWebsiteinfrontend/components/knowledge-actions-dropdown.tsxLine 154. That path does not refetch the listing afterward. As a result, the wildcard Knowledge table keeps the deleted website row until the next 5-second poll. The create mutation already invalidateslistFiles.Proposed fix
queryClient.invalidateQueries({ queryKey: ["search"] }); + queryClient.invalidateQueries({ queryKey: ["listFiles"] }); queryClient.invalidateQueries({ queryKey: [...TASKS_QUERY_KEY] });📝 Committable suggestion
🤖 Prompt for AI Agents