-
Notifications
You must be signed in to change notification settings - Fork 431
feat: improve ingest tracking events #1933
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
Changes from all commits
059de23
537c1c6
cd5d8d7
10edd27
1faaa01
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -35,6 +35,7 @@ import { useAuth } from "@/contexts/auth-context"; | |
| import { useIsCloudBrand } from "@/contexts/brand-context"; | ||
| import { useTask } from "@/contexts/task-context"; | ||
| import { usePermissions } from "@/hooks/use-permissions"; | ||
| import { trackProcessFailure, trackStartProcess } from "@/lib/analytics"; | ||
| import { | ||
| getConnectorDescriptor, | ||
| getConnectorDescriptors, | ||
|
|
@@ -333,11 +334,25 @@ export function KnowledgeDropdown() { | |
|
|
||
| const uploadFile = async (file: File, replace: boolean) => { | ||
| setFileUploading(true); | ||
| trackStartProcess({ | ||
| processType: "Ingestion", | ||
| process: "Document Upload", | ||
| category: "Knowledge", | ||
| source: "file", | ||
| total_files: 1, | ||
| }); | ||
|
|
||
| try { | ||
| await uploadFileUtil(file, replace); | ||
| refetchTasks(); | ||
| } catch (error) { | ||
| trackProcessFailure({ | ||
| processType: "Ingestion", | ||
| process: "Document Upload", | ||
| category: "Knowledge", | ||
| source: "file", | ||
| resultValue: error instanceof Error ? error.message : "Unknown error", | ||
| }); | ||
| // Dispatch event that chat context can listen to | ||
| // This avoids circular dependency issues | ||
| if (typeof window !== "undefined") { | ||
|
|
@@ -359,6 +374,14 @@ export function KnowledgeDropdown() { | |
| filesToUpload: File[], | ||
| replace: boolean, | ||
| ) => { | ||
| trackStartProcess({ | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (1c) [Normal] Batch folder uploads produce a 1:N start-to-end event ratioProblem
Background Information
Code References
Potential Solution
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we keep this for now unless there are issues w/ the amplitude dashboards. |
||
| processType: "Ingestion", | ||
| process: "Document Upload", | ||
| category: "Knowledge", | ||
| source: "folder", | ||
| total_files: filesToUpload.length, | ||
| }); | ||
|
|
||
| const batches: File[][] = []; | ||
| for (let i = 0; i < filesToUpload.length; i += uploadBatchSize) { | ||
| batches.push(filesToUpload.slice(i, i + uploadBatchSize)); | ||
|
|
@@ -371,8 +394,15 @@ export function KnowledgeDropdown() { | |
| for (const batch of batches) { | ||
| try { | ||
| const result = await uploadFiles(batch, replace); | ||
| addTask(result.taskId); | ||
| addTask(result.taskId, { source: "folder" }); | ||
| } catch (error) { | ||
| trackProcessFailure({ | ||
| processType: "Ingestion", | ||
| process: "Document Upload", | ||
| category: "Knowledge", | ||
| source: "folder", | ||
| resultValue: error instanceof Error ? error.message : "Unknown error", | ||
| }); | ||
| console.error("[Folder Upload] Batch upload failed:", error); | ||
| toast.error("Batch upload failed", { | ||
| description: error instanceof Error ? error.message : "Unknown error", | ||
|
|
@@ -577,6 +607,12 @@ export function KnowledgeDropdown() { | |
|
|
||
| setFolderLoading(true); | ||
| setShowFolderDialog(false); | ||
| trackStartProcess({ | ||
| processType: "Ingestion", | ||
| process: "Document Upload", | ||
| category: "Knowledge", | ||
| source: "path", | ||
| }); | ||
|
|
||
| try { | ||
| const response = await fetch("/api/upload_path", { | ||
|
|
@@ -596,7 +632,7 @@ export function KnowledgeDropdown() { | |
| throw new Error("No task ID received from server"); | ||
| } | ||
|
|
||
| addTask(taskId); | ||
| addTask(taskId, { source: "path" }); | ||
| setFolderPath(""); | ||
| // Refetch tasks to show the new task | ||
| refetchTasks(); | ||
|
|
@@ -613,6 +649,13 @@ export function KnowledgeDropdown() { | |
| } | ||
| } | ||
| } catch (error) { | ||
| trackProcessFailure({ | ||
| processType: "Ingestion", | ||
| process: "Document Upload", | ||
| category: "Knowledge", | ||
| source: "path", | ||
| resultValue: error instanceof Error ? error.message : "Unknown error", | ||
| }); | ||
| console.error("Folder upload error:", error); | ||
| } finally { | ||
| setFolderLoading(false); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -61,7 +61,10 @@ export interface TaskFile { | |
| interface TaskContextType { | ||
| tasks: Task[]; | ||
| files: TaskFile[]; | ||
| addTask: (taskId: string, options?: { connectorType?: string }) => void; | ||
| addTask: ( | ||
| taskId: string, | ||
| options?: { connectorType?: string; source?: string }, | ||
| ) => void; | ||
| addFiles: (files: Partial<TaskFile>[], taskId: string) => void; | ||
| /** Mark knowledge-table overlays as processing when a retry starts. */ | ||
| markTaskFilesProcessing: (taskId: string, sourceUrls: string[]) => void; | ||
|
|
@@ -100,17 +103,20 @@ export function TaskProvider({ children }: { children: React.ReactNode }) { | |
| const [isTaskDialogOpen, setIsTaskDialogOpen] = useState(false); | ||
| const previousTasksRef = useRef<Task[]>([]); | ||
| const taskConnectorTypesRef = useRef<Map<string, string>>(new Map()); | ||
| const taskSourcesRef = useRef<Map<string, string>>(new Map()); | ||
|
|
||
| const clearTaskConnectorType = useCallback((taskId: string) => { | ||
| const clearTaskMetadata = useCallback((taskId: string) => { | ||
| taskConnectorTypesRef.current.delete(taskId); | ||
| taskSourcesRef.current.delete(taskId); | ||
| }, []); | ||
|
|
||
| const clearTaskConnectorTypesWithoutOverlays = useCallback( | ||
| const clearTaskMetadataWithoutOverlays = useCallback( | ||
| (prevFiles: TaskFile[], nextFiles: TaskFile[]) => { | ||
| const nextTaskIds = new Set(nextFiles.map((file) => file.task_id)); | ||
| for (const file of prevFiles) { | ||
| if (!nextTaskIds.has(file.task_id)) { | ||
| taskConnectorTypesRef.current.delete(file.task_id); | ||
| taskSourcesRef.current.delete(file.task_id); | ||
| } | ||
| } | ||
| }, | ||
|
|
@@ -157,7 +163,7 @@ export function TaskProvider({ children }: { children: React.ReactNode }) { | |
| }, | ||
| ); | ||
|
|
||
| clearTaskConnectorType(variables.taskId); | ||
| clearTaskMetadata(variables.taskId); | ||
|
|
||
| // Update file to display as cancelled | ||
| setFiles((prevFiles) => | ||
|
|
@@ -248,7 +254,7 @@ export function TaskProvider({ children }: { children: React.ReactNode }) { | |
| const currentTaskIds = new Set(tasks.map((task) => task.task_id)); | ||
| for (const previousTask of previousTasksRef.current) { | ||
| if (!currentTaskIds.has(previousTask.task_id)) { | ||
| clearTaskConnectorType(previousTask.task_id); | ||
| clearTaskMetadata(previousTask.task_id); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -428,6 +434,16 @@ export function TaskProvider({ children }: { children: React.ReactNode }) { | |
| const failedFiles = getFailedFileCount(currentTask); | ||
| const isTotalFailure = failedFiles > 0 && successfulFiles === 0; | ||
|
|
||
| const firstFile = currentTask.files | ||
| ? Object.values(currentTask.files)[0] | ||
| : undefined; | ||
| const embeddingModel = firstFile?.embedding_model; | ||
| const connectorType = | ||
| taskConnectorTypesRef.current.get(currentTask.task_id) || "local"; | ||
| const source = | ||
| taskSourcesRef.current.get(currentTask.task_id) || | ||
| (connectorType === "local" ? "file" : "connector"); | ||
|
|
||
| if (isTotalFailure) { | ||
| trackProcessFailure({ | ||
| processType: "Ingestion", | ||
|
|
@@ -437,6 +453,9 @@ export function TaskProvider({ children }: { children: React.ReactNode }) { | |
| total_files: currentTask.total_files, | ||
| failed_files: failedFiles, | ||
| duration_seconds: currentTask.duration_seconds, | ||
| embedding_model: embeddingModel, | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (1d) [Minor]
|
||
| connector_type: connectorType, | ||
| source, | ||
| }); | ||
| } else { | ||
| trackProcessSuccess({ | ||
|
|
@@ -448,6 +467,9 @@ export function TaskProvider({ children }: { children: React.ReactNode }) { | |
| successful_files: successfulFiles, | ||
| failed_files: failedFiles, | ||
| duration_seconds: currentTask.duration_seconds, | ||
| embedding_model: embeddingModel, | ||
| connector_type: connectorType, | ||
| source, | ||
| }); | ||
| } | ||
|
|
||
|
|
@@ -541,7 +563,7 @@ export function TaskProvider({ children }: { children: React.ReactNode }) { | |
| } | ||
| } | ||
|
|
||
| clearTaskConnectorType(currentTask.task_id); | ||
| clearTaskMetadata(currentTask.task_id); | ||
|
|
||
| setFiles((prevFiles) => | ||
| prevFiles.filter((file) => { | ||
|
|
@@ -578,7 +600,7 @@ export function TaskProvider({ children }: { children: React.ReactNode }) { | |
| } | ||
| void refetchKnowledgeAfterTaskCompletion(); | ||
| } else if (taskJustReachedTerminal) { | ||
| clearTaskConnectorType(currentTask.task_id); | ||
| clearTaskMetadata(currentTask.task_id); | ||
| } | ||
|
|
||
| if ( | ||
|
|
@@ -632,16 +654,20 @@ export function TaskProvider({ children }: { children: React.ReactNode }) { | |
| tasks, | ||
| refetchSearch, | ||
| isOnboardingActive, | ||
| clearTaskConnectorType, | ||
| clearTaskMetadata, | ||
| queryClient, | ||
| ]); | ||
|
|
||
| const addTask = useCallback( | ||
| (taskId: string, options?: { connectorType?: string }) => { | ||
| (taskId: string, options?: { connectorType?: string; source?: string }) => { | ||
| const connectorType = options?.connectorType?.trim(); | ||
| if (connectorType) { | ||
| taskConnectorTypesRef.current.set(taskId, connectorType); | ||
| } | ||
| const source = options?.source?.trim(); | ||
| if (source) { | ||
| taskSourcesRef.current.set(taskId, source); | ||
| } | ||
| // React Query will automatically handle polling when tasks are active | ||
| // Just trigger a refetch to get the latest data | ||
| setTimeout(() => { | ||
|
|
@@ -656,11 +682,11 @@ export function TaskProvider({ children }: { children: React.ReactNode }) { | |
| const nextFiles = prevFiles.filter( | ||
| (file) => file.status !== "active" && file.status !== "failed", | ||
| ); | ||
| clearTaskConnectorTypesWithoutOverlays(prevFiles, nextFiles); | ||
| clearTaskMetadataWithoutOverlays(prevFiles, nextFiles); | ||
| return nextFiles; | ||
| }); | ||
| await refetchTasks(); | ||
| }, [refetchTasks, clearTaskConnectorTypesWithoutOverlays]); | ||
| }, [refetchTasks, clearTaskMetadataWithoutOverlays]); | ||
|
|
||
| const cancelTask = useCallback( | ||
| async (taskId: string) => { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -70,6 +70,16 @@ export const trackButton = <T = Record<string, unknown>>({ | |
| }: T & ButtonEventParams): void => | ||
| track("Button Clicked", { action, ...rest } as Record<string, unknown>); | ||
|
|
||
| interface StartProcessParams { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (1a) [Minor]
|
||
| processType: string; | ||
| process?: string; | ||
| category?: string; | ||
| } | ||
|
|
||
| export const trackStartProcess = <T = Record<string, unknown>>( | ||
| props: T & StartProcessParams, | ||
| ): void => track("Started Process", props as Record<string, unknown>); | ||
|
|
||
| interface EndProcessParams { | ||
| processType: string; | ||
| process?: string; | ||
|
|
||
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.
(1b) [Minor] "Started Process" event fires even when
uploadFileHTTP request fails — orphaned start eventProblem
knowledge-dropdown.tsx:uploadFile,trackStartProcessis called unconditionally before thetryblockuploadFileUtilthrows (e.g., network failure, 4xx/5xx response), thecatchblock shows an error toast but no task is created server-sidetask-context.tsxnever firestrackProcessSuccessortrackProcessFailureCode References
frontend/lib/analytics.tslines 79–81 (trackStartProcess)frontend/contexts/task-context.tsxlines 438–463 (completion event paths)Potential Solution
trackStartProcessinto thetryblock, after a successful response fromuploadFileUtil: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.
fixed by adding a failed event in the catch blocks