mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-15 18:25:18 +02:00
feat: improve connector popup with grouped OAuth connectors
Active Connectors tab: - Group OAuth connectors by type (Gmail, Google Drive, etc.) - Show account count badge on grouped cards - Show most recent last indexed date across all accounts - Show non-OAuth connectors individually with active task messages All Connectors tab: - Show most recent last indexed date for OAuth connector types - Check if any account is indexing for OAuth types Accounts List View: - Remove document count from individual account cards - Back button returns to previous tab (not always All Connectors) General: - Update handleViewAccountsList to use (connectorType, connectorTitle) signature - Consistent behavior for viewing accounts from both tabs
This commit is contained in:
parent
9ad1348d6b
commit
3ff87a218d
6 changed files with 193 additions and 93 deletions
|
|
@ -205,7 +205,6 @@ export const ConnectorIndicator: FC = () => {
|
||||||
connectors={(allConnectors || []) as SearchSourceConnector[]}
|
connectors={(allConnectors || []) as SearchSourceConnector[]}
|
||||||
indexingConnectorIds={indexingConnectorIds}
|
indexingConnectorIds={indexingConnectorIds}
|
||||||
logsSummary={logsSummary}
|
logsSummary={logsSummary}
|
||||||
documentTypeCounts={documentTypeCounts}
|
|
||||||
onBack={handleBackFromAccountsList}
|
onBack={handleBackFromAccountsList}
|
||||||
onManage={handleStartEdit}
|
onManage={handleStartEdit}
|
||||||
onAddAccount={() => {
|
onAddAccount={() => {
|
||||||
|
|
@ -317,18 +316,19 @@ export const ConnectorIndicator: FC = () => {
|
||||||
/>
|
/>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<ActiveConnectorsTab
|
<ActiveConnectorsTab
|
||||||
searchQuery={searchQuery}
|
searchQuery={searchQuery}
|
||||||
hasSources={hasSources}
|
hasSources={hasSources}
|
||||||
totalSourceCount={totalSourceCount}
|
totalSourceCount={totalSourceCount}
|
||||||
activeDocumentTypes={activeDocumentTypes}
|
activeDocumentTypes={activeDocumentTypes}
|
||||||
connectors={connectors as SearchSourceConnector[]}
|
connectors={connectors as SearchSourceConnector[]}
|
||||||
indexingConnectorIds={indexingConnectorIds}
|
indexingConnectorIds={indexingConnectorIds}
|
||||||
logsSummary={logsSummary}
|
logsSummary={logsSummary}
|
||||||
searchSpaceId={searchSpaceId}
|
searchSpaceId={searchSpaceId}
|
||||||
onTabChange={handleTabChange}
|
onTabChange={handleTabChange}
|
||||||
onManage={handleStartEdit}
|
onManage={handleStartEdit}
|
||||||
/>
|
onViewAccountsList={handleViewAccountsList}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* Bottom fade shadow */}
|
{/* Bottom fade shadow */}
|
||||||
|
|
|
||||||
|
|
@ -679,19 +679,20 @@ export const useConnectorDialog = () => {
|
||||||
|
|
||||||
// Handle viewing accounts list for OAuth connector type
|
// Handle viewing accounts list for OAuth connector type
|
||||||
const handleViewAccountsList = useCallback(
|
const handleViewAccountsList = useCallback(
|
||||||
(connector: (typeof OAUTH_CONNECTORS)[number]) => {
|
(connectorType: string, connectorTitle: string) => {
|
||||||
if (!searchSpaceId) return;
|
if (!searchSpaceId) return;
|
||||||
|
|
||||||
setViewingAccountsType({
|
setViewingAccountsType({
|
||||||
connectorType: connector.connectorType,
|
connectorType,
|
||||||
connectorTitle: connector.title,
|
connectorTitle,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update URL to show accounts view
|
// Update URL to show accounts view, preserving current tab
|
||||||
const url = new URL(window.location.href);
|
const url = new URL(window.location.href);
|
||||||
url.searchParams.set("modal", "connectors");
|
url.searchParams.set("modal", "connectors");
|
||||||
url.searchParams.set("view", "accounts");
|
url.searchParams.set("view", "accounts");
|
||||||
url.searchParams.set("connectorType", connector.connectorType);
|
url.searchParams.set("connectorType", connectorType);
|
||||||
|
// Keep the current tab in URL so we can go back to it
|
||||||
window.history.pushState({ modal: true }, "", url.toString());
|
window.history.pushState({ modal: true }, "", url.toString());
|
||||||
},
|
},
|
||||||
[searchSpaceId]
|
[searchSpaceId]
|
||||||
|
|
@ -702,7 +703,7 @@ export const useConnectorDialog = () => {
|
||||||
setViewingAccountsType(null);
|
setViewingAccountsType(null);
|
||||||
const url = new URL(window.location.href);
|
const url = new URL(window.location.href);
|
||||||
url.searchParams.set("modal", "connectors");
|
url.searchParams.set("modal", "connectors");
|
||||||
url.searchParams.set("tab", "all");
|
// Keep the current tab (don't change it) - just remove view-specific params
|
||||||
url.searchParams.delete("view");
|
url.searchParams.delete("view");
|
||||||
url.searchParams.delete("connectorType");
|
url.searchParams.delete("connectorType");
|
||||||
router.replace(url.pathname + url.search, { scroll: false });
|
router.replace(url.pathname + url.search, { scroll: false });
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,8 @@ import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
|
||||||
import type { SearchSourceConnector } from "@/contracts/types/connector.types";
|
import type { SearchSourceConnector } from "@/contracts/types/connector.types";
|
||||||
import type { LogActiveTask, LogSummary } from "@/contracts/types/log.types";
|
import type { LogActiveTask, LogSummary } from "@/contracts/types/log.types";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
import { OAUTH_CONNECTORS } from "../constants/connector-constants";
|
||||||
import { getDocumentCountForConnector } from "../utils/connector-document-mapping";
|
import { getDocumentCountForConnector } from "../utils/connector-document-mapping";
|
||||||
import { getConnectorDisplayName } from "./all-connectors-tab";
|
|
||||||
|
|
||||||
interface ActiveConnectorsTabProps {
|
interface ActiveConnectorsTabProps {
|
||||||
searchQuery: string;
|
searchQuery: string;
|
||||||
|
|
@ -25,6 +25,7 @@ interface ActiveConnectorsTabProps {
|
||||||
searchSpaceId: string;
|
searchSpaceId: string;
|
||||||
onTabChange: (value: string) => void;
|
onTabChange: (value: string) => void;
|
||||||
onManage?: (connector: SearchSourceConnector) => void;
|
onManage?: (connector: SearchSourceConnector) => void;
|
||||||
|
onViewAccountsList?: (connectorType: string, connectorTitle: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ActiveConnectorsTab: FC<ActiveConnectorsTabProps> = ({
|
export const ActiveConnectorsTab: FC<ActiveConnectorsTabProps> = ({
|
||||||
|
|
@ -37,6 +38,7 @@ export const ActiveConnectorsTab: FC<ActiveConnectorsTabProps> = ({
|
||||||
searchSpaceId,
|
searchSpaceId,
|
||||||
onTabChange,
|
onTabChange,
|
||||||
onManage,
|
onManage,
|
||||||
|
onViewAccountsList,
|
||||||
}) => {
|
}) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
|
@ -72,38 +74,24 @@ export const ActiveConnectorsTab: FC<ActiveConnectorsTabProps> = ({
|
||||||
const minutesAgo = differenceInMinutes(now, date);
|
const minutesAgo = differenceInMinutes(now, date);
|
||||||
const daysAgo = differenceInDays(now, date);
|
const daysAgo = differenceInDays(now, date);
|
||||||
|
|
||||||
// Just now (within last minute)
|
if (minutesAgo < 1) return "Just now";
|
||||||
if (minutesAgo < 1) {
|
if (minutesAgo < 60) return `${minutesAgo} ${minutesAgo === 1 ? "minute" : "minutes"} ago`;
|
||||||
return "Just now";
|
if (isToday(date)) return `Today at ${format(date, "h:mm a")}`;
|
||||||
}
|
if (isYesterday(date)) return `Yesterday at ${format(date, "h:mm a")}`;
|
||||||
|
if (daysAgo < 7) return `${daysAgo} ${daysAgo === 1 ? "day" : "days"} ago`;
|
||||||
// X minutes ago (less than 1 hour)
|
|
||||||
if (minutesAgo < 60) {
|
|
||||||
return `${minutesAgo} ${minutesAgo === 1 ? "minute" : "minutes"} ago`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Today at [time]
|
|
||||||
if (isToday(date)) {
|
|
||||||
return `Today at ${format(date, "h:mm a")}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Yesterday at [time]
|
|
||||||
if (isYesterday(date)) {
|
|
||||||
return `Yesterday at ${format(date, "h:mm a")}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// X days ago (less than 7 days)
|
|
||||||
if (daysAgo < 7) {
|
|
||||||
return `${daysAgo} ${daysAgo === 1 ? "day" : "days"} ago`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Full date for older entries
|
|
||||||
return format(date, "MMM d, yyyy");
|
return format(date, "MMM d, yyyy");
|
||||||
};
|
};
|
||||||
|
|
||||||
// Document types that should be shown as cards (not from connectors)
|
// Get most recent last indexed date from a list of connectors
|
||||||
// These are: EXTENSION (browser extension), FILE (uploaded files), NOTE (editor notes),
|
const getMostRecentLastIndexed = (connectorsList: SearchSourceConnector[]): string | undefined => {
|
||||||
// YOUTUBE_VIDEO (YouTube videos), and CRAWLED_URL (web pages - shown separately even though it can come from WEBCRAWLER_CONNECTOR)
|
return connectorsList.reduce<string | undefined>((latest, c) => {
|
||||||
|
if (!c.last_indexed_at) return latest;
|
||||||
|
if (!latest) return c.last_indexed_at;
|
||||||
|
return new Date(c.last_indexed_at) > new Date(latest) ? c.last_indexed_at : latest;
|
||||||
|
}, undefined);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Document types that should be shown as standalone cards (not from connectors)
|
||||||
const standaloneDocumentTypes = ["EXTENSION", "FILE", "NOTE", "YOUTUBE_VIDEO", "CRAWLED_URL"];
|
const standaloneDocumentTypes = ["EXTENSION", "FILE", "NOTE", "YOUTUBE_VIDEO", "CRAWLED_URL"];
|
||||||
|
|
||||||
// Filter to only show standalone document types that have documents (count > 0)
|
// Filter to only show standalone document types that have documents (count > 0)
|
||||||
|
|
@ -119,8 +107,47 @@ export const ActiveConnectorsTab: FC<ActiveConnectorsTabProps> = ({
|
||||||
return doc.label.toLowerCase().includes(searchQuery.toLowerCase());
|
return doc.label.toLowerCase().includes(searchQuery.toLowerCase());
|
||||||
});
|
});
|
||||||
|
|
||||||
// Filter connectors based on search query
|
// Get OAuth connector types set for quick lookup
|
||||||
const filteredConnectors = connectors.filter((connector) => {
|
const oauthConnectorTypes = new Set<string>(OAUTH_CONNECTORS.map((c) => c.connectorType));
|
||||||
|
|
||||||
|
// Separate OAuth and non-OAuth connectors
|
||||||
|
const oauthConnectors = connectors.filter((c) => oauthConnectorTypes.has(c.connector_type));
|
||||||
|
const nonOauthConnectors = connectors.filter((c) => !oauthConnectorTypes.has(c.connector_type));
|
||||||
|
|
||||||
|
// Group OAuth connectors by type
|
||||||
|
const oauthConnectorsByType = oauthConnectors.reduce(
|
||||||
|
(acc, connector) => {
|
||||||
|
const type = connector.connector_type;
|
||||||
|
if (!acc[type]) {
|
||||||
|
acc[type] = [];
|
||||||
|
}
|
||||||
|
acc[type].push(connector);
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{} as Record<string, SearchSourceConnector[]>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Get display info for OAuth connector type
|
||||||
|
const getOAuthConnectorTypeInfo = (connectorType: string) => {
|
||||||
|
const oauthConnector = OAUTH_CONNECTORS.find((c) => c.connectorType === connectorType);
|
||||||
|
return {
|
||||||
|
title: oauthConnector?.title || connectorType.replace(/_/g, " ").replace(/connector/gi, "").trim(),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Filter OAuth connector types based on search query
|
||||||
|
const filteredOAuthConnectorTypes = Object.entries(oauthConnectorsByType).filter(([connectorType]) => {
|
||||||
|
if (!searchQuery) return true;
|
||||||
|
const searchLower = searchQuery.toLowerCase();
|
||||||
|
const { title } = getOAuthConnectorTypeInfo(connectorType);
|
||||||
|
return (
|
||||||
|
title.toLowerCase().includes(searchLower) ||
|
||||||
|
connectorType.toLowerCase().includes(searchLower)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Filter non-OAuth connectors based on search query
|
||||||
|
const filteredNonOAuthConnectors = nonOauthConnectors.filter((connector) => {
|
||||||
if (!searchQuery) return true;
|
if (!searchQuery) return true;
|
||||||
const searchLower = searchQuery.toLowerCase();
|
const searchLower = searchQuery.toLowerCase();
|
||||||
return (
|
return (
|
||||||
|
|
@ -129,18 +156,98 @@ export const ActiveConnectorsTab: FC<ActiveConnectorsTabProps> = ({
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const hasActiveConnectors = filteredOAuthConnectorTypes.length > 0 || filteredNonOAuthConnectors.length > 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TabsContent value="active" className="m-0">
|
<TabsContent value="active" className="m-0">
|
||||||
{hasSources ? (
|
{hasSources ? (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Active Connectors Section */}
|
{/* Active Connectors Section */}
|
||||||
{filteredConnectors.length > 0 && (
|
{hasActiveConnectors && (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<h3 className="text-sm font-semibold text-muted-foreground">Active Connectors</h3>
|
<h3 className="text-sm font-semibold text-muted-foreground">Active Connectors</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||||
{filteredConnectors.map((connector) => {
|
{/* OAuth Connectors - Grouped by Type */}
|
||||||
|
{filteredOAuthConnectorTypes.map(([connectorType, typeConnectors]) => {
|
||||||
|
const { title } = getOAuthConnectorTypeInfo(connectorType);
|
||||||
|
const isAnyIndexing = typeConnectors.some(
|
||||||
|
(c: SearchSourceConnector) => indexingConnectorIds.has(c.id)
|
||||||
|
);
|
||||||
|
const documentCount = getDocumentCountForConnector(
|
||||||
|
connectorType,
|
||||||
|
documentTypeCounts
|
||||||
|
);
|
||||||
|
const accountCount = typeConnectors.length;
|
||||||
|
const mostRecentLastIndexed = getMostRecentLastIndexed(typeConnectors);
|
||||||
|
|
||||||
|
const handleManageClick = () => {
|
||||||
|
if (onViewAccountsList) {
|
||||||
|
onViewAccountsList(connectorType, title);
|
||||||
|
} else if (onManage && typeConnectors[0]) {
|
||||||
|
onManage(typeConnectors[0]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={`oauth-type-${connectorType}`}
|
||||||
|
className={cn(
|
||||||
|
"relative flex items-center gap-4 p-4 rounded-xl border border-border transition-all",
|
||||||
|
isAnyIndexing
|
||||||
|
? "bg-primary/5 border-primary/20"
|
||||||
|
: "bg-slate-400/5 dark:bg-white/5 hover:bg-slate-400/10 dark:hover:bg-white/10"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{/* Account count badge */}
|
||||||
|
<div className="absolute -top-2 -right-2 flex h-5 items-center justify-center rounded-md bg-primary px-2 text-[10px] font-semibold text-primary-foreground whitespace-nowrap">
|
||||||
|
{accountCount > 99 ? "99+" : accountCount} {accountCount === 1 ? "Account" : "Accounts"}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex h-12 w-12 items-center justify-center rounded-lg border shrink-0",
|
||||||
|
isAnyIndexing
|
||||||
|
? "bg-primary/10 border-primary/20"
|
||||||
|
: "bg-slate-400/5 dark:bg-white/5 border-slate-400/5 dark:border-white/5"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{getConnectorIcon(connectorType, "size-6")}
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<p className="text-[14px] font-semibold leading-tight truncate">
|
||||||
|
{title}
|
||||||
|
</p>
|
||||||
|
{isAnyIndexing ? (
|
||||||
|
<p className="text-[11px] text-primary mt-1 flex items-center gap-1.5">
|
||||||
|
<Loader2 className="size-3 animate-spin" />
|
||||||
|
Indexing...
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
<p className="text-[10px] text-muted-foreground mt-1 whitespace-nowrap">
|
||||||
|
{mostRecentLastIndexed
|
||||||
|
? `Last indexed: ${formatLastIndexedDate(mostRecentLastIndexed)}`
|
||||||
|
: "Never indexed"}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
<p className="text-[10px] text-muted-foreground mt-0.5">
|
||||||
|
{formatDocumentCount(documentCount)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
size="sm"
|
||||||
|
className="h-8 text-[11px] px-3 rounded-lg font-medium bg-white text-slate-700 hover:bg-slate-50 border-0 shadow-xs dark:bg-secondary dark:text-secondary-foreground dark:hover:bg-secondary/80 shrink-0"
|
||||||
|
onClick={handleManageClick}
|
||||||
|
>
|
||||||
|
Manage
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
{/* Non-OAuth Connectors - Individual Cards */}
|
||||||
|
{filteredNonOAuthConnectors.map((connector) => {
|
||||||
const isIndexing = indexingConnectorIds.has(connector.id);
|
const isIndexing = indexingConnectorIds.has(connector.id);
|
||||||
const activeTask = logsSummary?.active_tasks?.find(
|
const activeTask = logsSummary?.active_tasks?.find(
|
||||||
(task: LogActiveTask) => task.connector_id === connector.id
|
(task: LogActiveTask) => task.connector_id === connector.id
|
||||||
|
|
@ -162,7 +269,7 @@ export const ActiveConnectorsTab: FC<ActiveConnectorsTabProps> = ({
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex h-12 w-12 items-center justify-center rounded-lg border",
|
"flex h-12 w-12 items-center justify-center rounded-lg border shrink-0",
|
||||||
isIndexing
|
isIndexing
|
||||||
? "bg-primary/10 border-primary/20"
|
? "bg-primary/10 border-primary/20"
|
||||||
: "bg-slate-400/5 dark:bg-white/5 border-slate-400/5 dark:border-white/5"
|
: "bg-slate-400/5 dark:bg-white/5 border-slate-400/5 dark:border-white/5"
|
||||||
|
|
@ -172,7 +279,7 @@ export const ActiveConnectorsTab: FC<ActiveConnectorsTabProps> = ({
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<p className="text-[14px] font-semibold leading-tight truncate">
|
<p className="text-[14px] font-semibold leading-tight truncate">
|
||||||
{getConnectorDisplayName(connector.name)}
|
{connector.name}
|
||||||
</p>
|
</p>
|
||||||
{isIndexing ? (
|
{isIndexing ? (
|
||||||
<p className="text-[11px] text-primary mt-1 flex items-center gap-1.5">
|
<p className="text-[11px] text-primary mt-1 flex items-center gap-1.5">
|
||||||
|
|
@ -198,7 +305,7 @@ export const ActiveConnectorsTab: FC<ActiveConnectorsTabProps> = ({
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="h-8 text-[11px] px-3 rounded-lg font-medium bg-white text-slate-700 hover:bg-slate-50 border-0 shadow-xs dark:bg-secondary dark:text-secondary-foreground dark:hover:bg-secondary/80"
|
className="h-8 text-[11px] px-3 rounded-lg font-medium bg-white text-slate-700 hover:bg-slate-50 border-0 shadow-xs dark:bg-secondary dark:text-secondary-foreground dark:hover:bg-secondary/80 shrink-0"
|
||||||
onClick={onManage ? () => onManage(connector) : undefined}
|
onClick={onManage ? () => onManage(connector) : undefined}
|
||||||
>
|
>
|
||||||
Manage
|
Manage
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ interface AllConnectorsTabProps {
|
||||||
onCreateWebcrawler?: () => void;
|
onCreateWebcrawler?: () => void;
|
||||||
onCreateYouTubeCrawler?: () => void;
|
onCreateYouTubeCrawler?: () => void;
|
||||||
onManage?: (connector: SearchSourceConnector) => void;
|
onManage?: (connector: SearchSourceConnector) => void;
|
||||||
onViewAccountsList?: (connector: (typeof OAUTH_CONNECTORS)[number]) => void;
|
onViewAccountsList?: (connectorType: string, connectorTitle: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AllConnectorsTab: FC<AllConnectorsTabProps> = ({
|
export const AllConnectorsTab: FC<AllConnectorsTabProps> = ({
|
||||||
|
|
@ -102,25 +102,40 @@ export const AllConnectorsTab: FC<AllConnectorsTabProps> = ({
|
||||||
{filteredOAuth.map((connector) => {
|
{filteredOAuth.map((connector) => {
|
||||||
const isConnected = connectedTypes.has(connector.connectorType);
|
const isConnected = connectedTypes.has(connector.connectorType);
|
||||||
const isConnecting = connectingId === connector.id;
|
const isConnecting = connectingId === connector.id;
|
||||||
// Find the actual connector object if connected
|
|
||||||
const actualConnector =
|
// Find all connectors of this type
|
||||||
|
const typeConnectors =
|
||||||
isConnected && allConnectors
|
isConnected && allConnectors
|
||||||
? allConnectors.find(
|
? allConnectors.filter(
|
||||||
(c: SearchSourceConnector) =>
|
(c: SearchSourceConnector) =>
|
||||||
c.connector_type === connector.connectorType
|
c.connector_type === connector.connectorType
|
||||||
)
|
)
|
||||||
: undefined;
|
: [];
|
||||||
|
|
||||||
|
// Get the most recent last_indexed_at across all accounts
|
||||||
|
const mostRecentLastIndexed = typeConnectors.reduce<string | undefined>(
|
||||||
|
(latest, c) => {
|
||||||
|
if (!c.last_indexed_at) return latest;
|
||||||
|
if (!latest) return c.last_indexed_at;
|
||||||
|
return new Date(c.last_indexed_at) > new Date(latest) ? c.last_indexed_at : latest;
|
||||||
|
},
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
|
||||||
const documentCount = getDocumentCountForConnector(
|
const documentCount = getDocumentCountForConnector(
|
||||||
connector.connectorType,
|
connector.connectorType,
|
||||||
documentTypeCounts
|
documentTypeCounts
|
||||||
);
|
);
|
||||||
const isIndexing =
|
|
||||||
actualConnector &&
|
// Check if any account is currently indexing
|
||||||
indexingConnectorIds?.has(actualConnector.id);
|
const isIndexing = typeConnectors.some(
|
||||||
const activeTask = actualConnector
|
(c) => indexingConnectorIds?.has(c.id)
|
||||||
? getActiveTaskForConnector(actualConnector.id)
|
);
|
||||||
: undefined;
|
|
||||||
|
// Get active task from any indexing account
|
||||||
|
const activeTask = typeConnectors
|
||||||
|
.map((c) => getActiveTaskForConnector(c.id))
|
||||||
|
.find((task) => task !== undefined);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ConnectorCard
|
<ConnectorCard
|
||||||
|
|
@ -132,13 +147,13 @@ export const AllConnectorsTab: FC<AllConnectorsTabProps> = ({
|
||||||
isConnected={isConnected}
|
isConnected={isConnected}
|
||||||
isConnecting={isConnecting}
|
isConnecting={isConnecting}
|
||||||
documentCount={documentCount}
|
documentCount={documentCount}
|
||||||
lastIndexedAt={actualConnector?.last_indexed_at}
|
lastIndexedAt={mostRecentLastIndexed}
|
||||||
isIndexing={isIndexing}
|
isIndexing={isIndexing}
|
||||||
activeTask={activeTask}
|
activeTask={activeTask}
|
||||||
onConnect={() => onConnectOAuth(connector)}
|
onConnect={() => onConnectOAuth(connector)}
|
||||||
onManage={
|
onManage={
|
||||||
isConnected && onViewAccountsList
|
isConnected && onViewAccountsList
|
||||||
? () => onViewAccountsList(connector)
|
? () => onViewAccountsList(connector.connectorType, connector.title)
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
|
||||||
import type { SearchSourceConnector } from "@/contracts/types/connector.types";
|
import type { SearchSourceConnector } from "@/contracts/types/connector.types";
|
||||||
import type { LogActiveTask, LogSummary } from "@/contracts/types/log.types";
|
import type { LogActiveTask, LogSummary } from "@/contracts/types/log.types";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { getDocumentCountForConnector } from "../utils/connector-document-mapping";
|
|
||||||
import { getConnectorDisplayName } from "../tabs/all-connectors-tab";
|
import { getConnectorDisplayName } from "../tabs/all-connectors-tab";
|
||||||
|
|
||||||
interface ConnectorAccountsListViewProps {
|
interface ConnectorAccountsListViewProps {
|
||||||
|
|
@ -17,27 +16,12 @@ interface ConnectorAccountsListViewProps {
|
||||||
connectors: SearchSourceConnector[];
|
connectors: SearchSourceConnector[];
|
||||||
indexingConnectorIds: Set<number>;
|
indexingConnectorIds: Set<number>;
|
||||||
logsSummary: LogSummary | undefined;
|
logsSummary: LogSummary | undefined;
|
||||||
documentTypeCounts?: Record<string, number>;
|
|
||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
onManage: (connector: SearchSourceConnector) => void;
|
onManage: (connector: SearchSourceConnector) => void;
|
||||||
onAddAccount: () => void;
|
onAddAccount: () => void;
|
||||||
isConnecting?: boolean;
|
isConnecting?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Format document count (e.g., "1.2k docs", "500 docs", "1.5M docs")
|
|
||||||
*/
|
|
||||||
function formatDocumentCount(count: number | undefined): string {
|
|
||||||
if (count === undefined || count === 0) return "0 docs";
|
|
||||||
if (count < 1000) return `${count} docs`;
|
|
||||||
if (count < 1000000) {
|
|
||||||
const k = (count / 1000).toFixed(1);
|
|
||||||
return `${k.replace(/\.0$/, "")}k docs`;
|
|
||||||
}
|
|
||||||
const m = (count / 1000000).toFixed(1);
|
|
||||||
return `${m.replace(/\.0$/, "")}M docs`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format last indexed date with contextual messages
|
* Format last indexed date with contextual messages
|
||||||
*/
|
*/
|
||||||
|
|
@ -76,7 +60,6 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
|
||||||
connectors,
|
connectors,
|
||||||
indexingConnectorIds,
|
indexingConnectorIds,
|
||||||
logsSummary,
|
logsSummary,
|
||||||
documentTypeCounts,
|
|
||||||
onBack,
|
onBack,
|
||||||
onManage,
|
onManage,
|
||||||
onAddAccount,
|
onAddAccount,
|
||||||
|
|
@ -145,10 +128,6 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
|
||||||
const activeTask = logsSummary?.active_tasks?.find(
|
const activeTask = logsSummary?.active_tasks?.find(
|
||||||
(task: LogActiveTask) => task.connector_id === connector.id
|
(task: LogActiveTask) => task.connector_id === connector.id
|
||||||
);
|
);
|
||||||
const documentCount = getDocumentCountForConnector(
|
|
||||||
connector.connector_type,
|
|
||||||
documentTypeCounts
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|
@ -191,9 +170,6 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
|
||||||
: "Never indexed"}
|
: "Never indexed"}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
<p className="text-[10px] text-muted-foreground mt-0.5">
|
|
||||||
{formatDocumentCount(documentCount)}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ export const getConnectorTypeDisplay = (type: string): string => {
|
||||||
CLICKUP_CONNECTOR: "ClickUp",
|
CLICKUP_CONNECTOR: "ClickUp",
|
||||||
GOOGLE_CALENDAR_CONNECTOR: "Google Calendar",
|
GOOGLE_CALENDAR_CONNECTOR: "Google Calendar",
|
||||||
GOOGLE_GMAIL_CONNECTOR: "Google Gmail",
|
GOOGLE_GMAIL_CONNECTOR: "Google Gmail",
|
||||||
|
GOOGLE_DRIVE_CONNECTOR: "Google Drive",
|
||||||
AIRTABLE_CONNECTOR: "Airtable",
|
AIRTABLE_CONNECTOR: "Airtable",
|
||||||
LUMA_CONNECTOR: "Luma",
|
LUMA_CONNECTOR: "Luma",
|
||||||
ELASTICSEARCH_CONNECTOR: "Elasticsearch",
|
ELASTICSEARCH_CONNECTOR: "Elasticsearch",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue