mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-02 20:32:39 +02:00
feat: improve Google Calendar and Gmail connectors with enhanced error handling
- Added user-friendly re-authentication messages for expired or revoked tokens in both Google Calendar and Gmail connectors. - Updated error handling in indexing tasks to log specific authentication errors and provide clearer feedback to users. - Enhanced the connector UI to handle indexing failures more effectively, improving overall user experience.
This commit is contained in:
parent
29382070aa
commit
8d8f69545e
8 changed files with 113 additions and 12 deletions
|
|
@ -5,12 +5,14 @@ import { Cable, Loader2 } from "lucide-react";
|
|||
import { useSearchParams } from "next/navigation";
|
||||
import type { FC } from "react";
|
||||
import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms";
|
||||
import { currentUserAtom } from "@/atoms/user/user-query.atoms";
|
||||
import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
|
||||
import { Dialog, DialogContent } from "@/components/ui/dialog";
|
||||
import { Tabs, TabsContent } from "@/components/ui/tabs";
|
||||
import type { SearchSourceConnector } from "@/contracts/types/connector.types";
|
||||
import { useConnectorsElectric } from "@/hooks/use-connectors-electric";
|
||||
import { useDocumentsElectric } from "@/hooks/use-documents-electric";
|
||||
import { useInbox } from "@/hooks/use-inbox";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { ConnectorDialogHeader } from "./connector-popup/components/connector-dialog-header";
|
||||
import { ConnectorConnectView } from "./connector-popup/connector-configs/views/connector-connect-view";
|
||||
|
|
@ -27,10 +29,18 @@ import { YouTubeCrawlerView } from "./connector-popup/views/youtube-crawler-view
|
|||
export const ConnectorIndicator: FC = () => {
|
||||
const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom);
|
||||
const searchParams = useSearchParams();
|
||||
const { data: currentUser } = useAtomValue(currentUserAtom);
|
||||
|
||||
// Fetch document type counts using Electric SQL + PGlite for real-time updates
|
||||
const { documentTypeCounts, loading: documentTypesLoading } = useDocumentsElectric(searchSpaceId);
|
||||
|
||||
// Fetch notifications to detect indexing failures
|
||||
const { inboxItems = [] } = useInbox(
|
||||
currentUser?.id ?? null,
|
||||
searchSpaceId ? Number(searchSpaceId) : null,
|
||||
"connector_indexing"
|
||||
);
|
||||
|
||||
// Check if YouTube view is active
|
||||
const isYouTubeView = searchParams.get("view") === "youtube";
|
||||
|
||||
|
|
@ -116,8 +126,10 @@ export const ConnectorIndicator: FC = () => {
|
|||
};
|
||||
|
||||
// Track indexing state locally - clears automatically when Electric SQL detects last_indexed_at changed
|
||||
const { indexingConnectorIds, startIndexing } = useIndexingConnectors(
|
||||
connectors as SearchSourceConnector[]
|
||||
// Also clears when failed notifications are detected
|
||||
const { indexingConnectorIds, startIndexing, stopIndexing } = useIndexingConnectors(
|
||||
connectors as SearchSourceConnector[],
|
||||
inboxItems
|
||||
);
|
||||
|
||||
const isLoading = connectorsLoading || documentTypesLoading;
|
||||
|
|
@ -246,7 +258,7 @@ export const ConnectorIndicator: FC = () => {
|
|||
editingConnector.connector_type !== "GOOGLE_DRIVE_CONNECTOR"
|
||||
? () => {
|
||||
startIndexing(editingConnector.id);
|
||||
handleQuickIndexConnector(editingConnector.id, editingConnector.connector_type);
|
||||
handleQuickIndexConnector(editingConnector.id, editingConnector.connector_type, stopIndexing);
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
|
|||
};
|
||||
}, [checkScrollState]);
|
||||
|
||||
// Reset local quick indexing state when indexing completes
|
||||
// Reset local quick indexing state when indexing completes or fails
|
||||
useEffect(() => {
|
||||
if (!isIndexing) {
|
||||
setIsQuickIndexing(false);
|
||||
|
|
|
|||
|
|
@ -1375,7 +1375,7 @@ export const useConnectorDialog = () => {
|
|||
|
||||
// Handle quick index (index without date picker, uses backend defaults)
|
||||
const handleQuickIndexConnector = useCallback(
|
||||
async (connectorId: number, connectorType?: string) => {
|
||||
async (connectorId: number, connectorType?: string, stopIndexing?: (id: number) => void) => {
|
||||
if (!searchSpaceId) return;
|
||||
|
||||
// Track quick index clicked event
|
||||
|
|
@ -1401,6 +1401,10 @@ export const useConnectorDialog = () => {
|
|||
} catch (error) {
|
||||
console.error("Error indexing connector content:", error);
|
||||
toast.error(error instanceof Error ? error.message : "Failed to start indexing");
|
||||
// Stop indexing state on error
|
||||
if (stopIndexing) {
|
||||
stopIndexing(connectorId);
|
||||
}
|
||||
}
|
||||
},
|
||||
[searchSpaceId, indexConnector]
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import type { SearchSourceConnector } from "@/contracts/types/connector.types";
|
||||
import type { InboxItem } from "@/contracts/types/inbox.types";
|
||||
import { isConnectorIndexingMetadata } from "@/contracts/types/inbox.types";
|
||||
|
||||
/**
|
||||
* Hook to track which connectors are currently indexing using local state.
|
||||
|
|
@ -9,10 +11,14 @@ import type { SearchSourceConnector } from "@/contracts/types/connector.types";
|
|||
* This provides a better UX than polling by:
|
||||
* 1. Setting indexing state immediately when user triggers indexing (optimistic)
|
||||
* 2. Clearing indexing state when Electric SQL detects last_indexed_at changed
|
||||
* 3. Clearing indexing state when a failed notification is detected
|
||||
*
|
||||
* The actual `last_indexed_at` value comes from Electric SQL/PGlite, not local state.
|
||||
*/
|
||||
export function useIndexingConnectors(connectors: SearchSourceConnector[]) {
|
||||
export function useIndexingConnectors(
|
||||
connectors: SearchSourceConnector[],
|
||||
inboxItems?: InboxItem[]
|
||||
) {
|
||||
// Set of connector IDs that are currently indexing
|
||||
const [indexingConnectorIds, setIndexingConnectorIds] = useState<Set<number>>(new Set());
|
||||
|
||||
|
|
@ -48,6 +54,40 @@ export function useIndexingConnectors(connectors: SearchSourceConnector[]) {
|
|||
}
|
||||
}, [connectors, indexingConnectorIds]);
|
||||
|
||||
// Detect failed notifications and stop indexing state
|
||||
useEffect(() => {
|
||||
if (!inboxItems || inboxItems.length === 0) return;
|
||||
|
||||
const newIndexingIds = new Set(indexingConnectorIds);
|
||||
let hasChanges = false;
|
||||
|
||||
for (const item of inboxItems) {
|
||||
// Only check connector_indexing notifications
|
||||
if (item.type !== "connector_indexing") continue;
|
||||
|
||||
// Check if this notification indicates a failure
|
||||
const metadata = isConnectorIndexingMetadata(item.metadata)
|
||||
? item.metadata
|
||||
: null;
|
||||
if (!metadata) continue;
|
||||
|
||||
// Check if status is "failed" or if there's an error_message
|
||||
const isFailed =
|
||||
metadata.status === "failed" ||
|
||||
(metadata.error_message && metadata.error_message.trim().length > 0);
|
||||
|
||||
// If failed and connector is in indexing state, clear it
|
||||
if (isFailed && indexingConnectorIds.has(metadata.connector_id)) {
|
||||
newIndexingIds.delete(metadata.connector_id);
|
||||
hasChanges = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasChanges) {
|
||||
setIndexingConnectorIds(newIndexingIds);
|
||||
}
|
||||
}, [inboxItems, indexingConnectorIds]);
|
||||
|
||||
// Add a connector to the indexing set (called when indexing starts)
|
||||
const startIndexing = useCallback((connectorId: number) => {
|
||||
setIndexingConnectorIds((prev) => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue