mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-06-08 20:25:19 +02:00
Merge pull request #680 from AnishSarkar22/fix/index-future-date
feat: Index future dates for calendar based connectors & bug fixes
This commit is contained in:
commit
383592ce63
9 changed files with 142 additions and 68 deletions
|
|
@ -543,7 +543,7 @@ async def index_connector_content(
|
|||
),
|
||||
end_date: str = Query(
|
||||
None,
|
||||
description="End date for indexing (YYYY-MM-DD format). If not provided, uses today's date",
|
||||
description="End date for indexing (YYYY-MM-DD format). If not provided, uses today's date. For calendar connectors (Google Calendar, Luma), future dates can be selected to index upcoming events.",
|
||||
),
|
||||
drive_items: GoogleDriveIndexRequest | None = Body(
|
||||
None,
|
||||
|
|
@ -617,7 +617,19 @@ async def index_connector_content(
|
|||
else:
|
||||
indexing_from = start_date
|
||||
|
||||
indexing_to = end_date if end_date else today_str
|
||||
# For calendar connectors, default to today but allow future dates if explicitly provided
|
||||
if connector.connector_type in [
|
||||
SearchSourceConnectorType.GOOGLE_CALENDAR_CONNECTOR,
|
||||
SearchSourceConnectorType.LUMA_CONNECTOR,
|
||||
]:
|
||||
# Default to today if no end_date provided (users can manually select future dates)
|
||||
if end_date is None:
|
||||
indexing_to = today_str
|
||||
else:
|
||||
indexing_to = end_date
|
||||
else:
|
||||
# For non-calendar connectors, cap at today
|
||||
indexing_to = end_date if end_date else today_str
|
||||
|
||||
if connector.connector_type == SearchSourceConnectorType.SLACK_CONNECTOR:
|
||||
from app.tasks.celery_tasks.connector_tasks import (
|
||||
|
|
|
|||
|
|
@ -45,8 +45,9 @@ async def index_google_calendar_events(
|
|||
connector_id: ID of the Google Calendar connector
|
||||
search_space_id: ID of the search space to store documents in
|
||||
user_id: User ID
|
||||
start_date: Start date for indexing (YYYY-MM-DD format)
|
||||
end_date: End date for indexing (YYYY-MM-DD format)
|
||||
start_date: Start date for indexing (YYYY-MM-DD format). Can be in the past or future.
|
||||
end_date: End date for indexing (YYYY-MM-DD format). Can be in the future to index upcoming events.
|
||||
Defaults to today if not provided.
|
||||
update_last_indexed: Whether to update the last_indexed_at timestamp (default: True)
|
||||
|
||||
Returns:
|
||||
|
|
@ -165,8 +166,10 @@ async def index_google_calendar_events(
|
|||
end_date = None
|
||||
|
||||
# Calculate date range
|
||||
# For calendar connectors, allow future dates to index upcoming events
|
||||
if start_date is None or end_date is None:
|
||||
# Fall back to calculating dates based on last_indexed_at
|
||||
# Default to today (users can manually select future dates if needed)
|
||||
calculated_end_date = datetime.now()
|
||||
|
||||
# Use last_indexed_at as start date if available, otherwise use 30 days ago
|
||||
|
|
@ -178,19 +181,13 @@ async def index_google_calendar_events(
|
|||
else connector.last_indexed_at
|
||||
)
|
||||
|
||||
# Check if last_indexed_at is in the future or after end_date
|
||||
if last_indexed_naive > calculated_end_date:
|
||||
logger.warning(
|
||||
f"Last indexed date ({last_indexed_naive.strftime('%Y-%m-%d')}) is in the future. Using 30 days ago instead."
|
||||
)
|
||||
calculated_start_date = calculated_end_date - timedelta(days=30)
|
||||
else:
|
||||
calculated_start_date = last_indexed_naive
|
||||
logger.info(
|
||||
f"Using last_indexed_at ({calculated_start_date.strftime('%Y-%m-%d')}) as start date"
|
||||
)
|
||||
# Allow future dates - use last_indexed_at as start date
|
||||
calculated_start_date = last_indexed_naive
|
||||
logger.info(
|
||||
f"Using last_indexed_at ({calculated_start_date.strftime('%Y-%m-%d')}) as start date"
|
||||
)
|
||||
else:
|
||||
calculated_start_date = calculated_end_date - timedelta(
|
||||
calculated_start_date = datetime.now() - timedelta(
|
||||
days=30
|
||||
) # Use 30 days as default for calendar events
|
||||
logger.info(
|
||||
|
|
@ -205,7 +202,7 @@ async def index_google_calendar_events(
|
|||
end_date if end_date else calculated_end_date.strftime("%Y-%m-%d")
|
||||
)
|
||||
else:
|
||||
# Use provided dates
|
||||
# Use provided dates (including future dates)
|
||||
start_date_str = start_date
|
||||
end_date_str = end_date
|
||||
|
||||
|
|
|
|||
|
|
@ -45,8 +45,9 @@ async def index_luma_events(
|
|||
connector_id: ID of the Luma connector
|
||||
search_space_id: ID of the search space to store documents in
|
||||
user_id: User ID
|
||||
start_date: Start date for indexing (YYYY-MM-DD format)
|
||||
end_date: End date for indexing (YYYY-MM-DD format)
|
||||
start_date: Start date for indexing (YYYY-MM-DD format). Can be in the past or future.
|
||||
end_date: End date for indexing (YYYY-MM-DD format). Can be in the future to index upcoming events.
|
||||
Defaults to today if not provided.
|
||||
update_last_indexed: Whether to update the last_indexed_at timestamp (default: True)
|
||||
|
||||
Returns:
|
||||
|
|
@ -116,8 +117,10 @@ async def index_luma_events(
|
|||
luma_client = LumaConnector(api_key=api_key)
|
||||
|
||||
# Calculate date range
|
||||
# For calendar connectors, allow future dates to index upcoming events
|
||||
if start_date is None or end_date is None:
|
||||
# Fall back to calculating dates based on last_indexed_at
|
||||
# Default to today (users can manually select future dates if needed)
|
||||
calculated_end_date = datetime.now()
|
||||
|
||||
# Use last_indexed_at as start date if available, otherwise use 30 days ago
|
||||
|
|
@ -129,19 +132,13 @@ async def index_luma_events(
|
|||
else connector.last_indexed_at
|
||||
)
|
||||
|
||||
# Check if last_indexed_at is in the future or after end_date
|
||||
if last_indexed_naive > calculated_end_date:
|
||||
logger.warning(
|
||||
f"Last indexed date ({last_indexed_naive.strftime('%Y-%m-%d')}) is in the future. Using 30 days ago instead."
|
||||
)
|
||||
calculated_start_date = calculated_end_date - timedelta(days=30)
|
||||
else:
|
||||
calculated_start_date = last_indexed_naive
|
||||
logger.info(
|
||||
f"Using last_indexed_at ({calculated_start_date.strftime('%Y-%m-%d')}) as start date"
|
||||
)
|
||||
# Allow future dates - use last_indexed_at as start date
|
||||
calculated_start_date = last_indexed_naive
|
||||
logger.info(
|
||||
f"Using last_indexed_at ({calculated_start_date.strftime('%Y-%m-%d')}) as start date"
|
||||
)
|
||||
else:
|
||||
calculated_start_date = calculated_end_date - timedelta(days=30)
|
||||
calculated_start_date = datetime.now() - timedelta(days=30)
|
||||
logger.info(
|
||||
f"No last_indexed_at found, using {calculated_start_date.strftime('%Y-%m-%d')} (30 days ago) as start date"
|
||||
)
|
||||
|
|
@ -154,7 +151,7 @@ async def index_luma_events(
|
|||
end_date if end_date else calculated_end_date.strftime("%Y-%m-%d")
|
||||
)
|
||||
else:
|
||||
# Use provided dates
|
||||
# Use provided dates (including future dates)
|
||||
start_date_str = start_date
|
||||
end_date_str = end_date
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import { format, subDays, subYears } from "date-fns";
|
||||
import { addDays, format, subDays, subYears } from "date-fns";
|
||||
import { Calendar as CalendarIcon } from "lucide-react";
|
||||
import type { FC } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
|
@ -14,6 +14,7 @@ interface DateRangeSelectorProps {
|
|||
endDate: Date | undefined;
|
||||
onStartDateChange: (date: Date | undefined) => void;
|
||||
onEndDateChange: (date: Date | undefined) => void;
|
||||
allowFutureDates?: boolean; // Allow future dates for calendar connectors
|
||||
}
|
||||
|
||||
export const DateRangeSelector: FC<DateRangeSelectorProps> = ({
|
||||
|
|
@ -21,6 +22,7 @@ export const DateRangeSelector: FC<DateRangeSelectorProps> = ({
|
|||
endDate,
|
||||
onStartDateChange,
|
||||
onEndDateChange,
|
||||
allowFutureDates = false,
|
||||
}) => {
|
||||
const handleLast30Days = () => {
|
||||
const today = new Date();
|
||||
|
|
@ -28,6 +30,12 @@ export const DateRangeSelector: FC<DateRangeSelectorProps> = ({
|
|||
onEndDateChange(today);
|
||||
};
|
||||
|
||||
const handleNext30Days = () => {
|
||||
const today = new Date();
|
||||
onStartDateChange(today);
|
||||
onEndDateChange(addDays(today, 30));
|
||||
};
|
||||
|
||||
const handleLastYear = () => {
|
||||
const today = new Date();
|
||||
onStartDateChange(subYears(today, 1));
|
||||
|
|
@ -43,8 +51,9 @@ export const DateRangeSelector: FC<DateRangeSelectorProps> = ({
|
|||
<div className="rounded-xl bg-slate-400/5 dark:bg-white/5 p-3 sm:p-6">
|
||||
<h3 className="font-medium text-sm sm:text-base mb-4">Select Date Range</h3>
|
||||
<p className="text-xs sm:text-sm text-muted-foreground mb-6">
|
||||
Choose how far back you want to sync your data. You can always re-index later with different
|
||||
dates.
|
||||
{allowFutureDates
|
||||
? "Choose the date range to sync your data. You can select future dates to index upcoming events."
|
||||
: "Choose how far back you want to sync your data. You can always re-index later with different dates."}
|
||||
</p>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
|
|
@ -72,7 +81,7 @@ export const DateRangeSelector: FC<DateRangeSelectorProps> = ({
|
|||
mode="single"
|
||||
selected={startDate}
|
||||
onSelect={onStartDateChange}
|
||||
disabled={(date) => date > new Date()}
|
||||
disabled={allowFutureDates ? false : (date) => date > new Date()}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
|
@ -102,7 +111,11 @@ export const DateRangeSelector: FC<DateRangeSelectorProps> = ({
|
|||
mode="single"
|
||||
selected={endDate}
|
||||
onSelect={onEndDateChange}
|
||||
disabled={(date) => date > new Date() || (startDate ? date < startDate : false)}
|
||||
disabled={
|
||||
allowFutureDates
|
||||
? (date) => (startDate ? date < startDate : false)
|
||||
: (date) => date > new Date() || (startDate ? date < startDate : false)
|
||||
}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
|
@ -129,6 +142,17 @@ export const DateRangeSelector: FC<DateRangeSelectorProps> = ({
|
|||
>
|
||||
Last 30 Days
|
||||
</Button>
|
||||
{allowFutureDates && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleNext30Days}
|
||||
className="text-xs sm:text-sm bg-slate-400/5 dark:bg-slate-400/5 border-slate-400/20 hover:bg-slate-400/10 dark:hover:bg-slate-400/10"
|
||||
>
|
||||
Next 30 Days
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
|
|
|
|||
|
|
@ -173,6 +173,7 @@ export const LumaConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting }
|
|||
endDate={endDate}
|
||||
onStartDateChange={setStartDate}
|
||||
onEndDateChange={setEndDate}
|
||||
allowFutureDates={true}
|
||||
/>
|
||||
|
||||
{/* Periodic Sync Config */}
|
||||
|
|
|
|||
|
|
@ -211,6 +211,10 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
|
|||
endDate={endDate}
|
||||
onStartDateChange={onStartDateChange}
|
||||
onEndDateChange={onEndDateChange}
|
||||
allowFutureDates={
|
||||
connector.connector_type === "GOOGLE_CALENDAR_CONNECTOR" ||
|
||||
connector.connector_type === "LUMA_CONNECTOR"
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
|
|
|||
|
|
@ -159,6 +159,10 @@ export const IndexingConfigurationView: FC<IndexingConfigurationViewProps> = ({
|
|||
endDate={endDate}
|
||||
onStartDateChange={onStartDateChange}
|
||||
onEndDateChange={onEndDateChange}
|
||||
allowFutureDates={
|
||||
config.connectorType === "GOOGLE_CALENDAR_CONNECTOR" ||
|
||||
config.connectorType === "LUMA_CONNECTOR"
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
|
|
|||
|
|
@ -80,6 +80,12 @@ export const useConnectorDialog = () => {
|
|||
connectorTitle: string;
|
||||
} | null>(null);
|
||||
|
||||
// Track if we came from accounts list when entering edit mode
|
||||
const [cameFromAccountsList, setCameFromAccountsList] = useState<{
|
||||
connectorType: string;
|
||||
connectorTitle: string;
|
||||
} | null>(null);
|
||||
|
||||
// Helper function to get frequency label
|
||||
const getFrequencyLabel = useCallback((minutes: string): string => {
|
||||
switch (minutes) {
|
||||
|
|
@ -960,6 +966,14 @@ export const useConnectorDialog = () => {
|
|||
return;
|
||||
}
|
||||
|
||||
// Track if we came from accounts list view
|
||||
// If viewingAccountsType matches this connector type, preserve it
|
||||
if (viewingAccountsType && viewingAccountsType.connectorType === connector.connector_type) {
|
||||
setCameFromAccountsList(viewingAccountsType);
|
||||
} else {
|
||||
setCameFromAccountsList(null);
|
||||
}
|
||||
|
||||
// Track index with date range opened event
|
||||
if (connector.is_indexable) {
|
||||
trackIndexWithDateRangeOpened(
|
||||
|
|
@ -989,7 +1003,7 @@ export const useConnectorDialog = () => {
|
|||
url.searchParams.set("connectorId", connector.id.toString());
|
||||
window.history.pushState({ modal: true }, "", url.toString());
|
||||
},
|
||||
[searchSpaceId]
|
||||
[searchSpaceId, viewingAccountsType]
|
||||
);
|
||||
|
||||
// Handle saving connector changes
|
||||
|
|
@ -1252,13 +1266,30 @@ export const useConnectorDialog = () => {
|
|||
|
||||
// Handle going back from edit view
|
||||
const handleBackFromEdit = useCallback(() => {
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set("modal", "connectors");
|
||||
url.searchParams.set("tab", "all");
|
||||
url.searchParams.delete("view");
|
||||
url.searchParams.delete("connectorId");
|
||||
router.replace(url.pathname + url.search, { scroll: false });
|
||||
}, [router]);
|
||||
// If we came from accounts list view, go back there
|
||||
if (cameFromAccountsList && editingConnector) {
|
||||
// Restore accounts list view
|
||||
setViewingAccountsType(cameFromAccountsList);
|
||||
setCameFromAccountsList(null);
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set("modal", "connectors");
|
||||
url.searchParams.set("view", "accounts");
|
||||
url.searchParams.set("connectorType", cameFromAccountsList.connectorType);
|
||||
url.searchParams.delete("connectorId");
|
||||
router.replace(url.pathname + url.search, { scroll: false });
|
||||
} else {
|
||||
// Otherwise, go back to main connector popup
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set("modal", "connectors");
|
||||
url.searchParams.set("tab", "all");
|
||||
url.searchParams.delete("view");
|
||||
url.searchParams.delete("connectorId");
|
||||
router.replace(url.pathname + url.search, { scroll: false });
|
||||
}
|
||||
setEditingConnector(null);
|
||||
setConnectorName(null);
|
||||
setConnectorConfig(null);
|
||||
}, [router, cameFromAccountsList, editingConnector]);
|
||||
|
||||
// Handle dialog open/close
|
||||
const handleOpenChange = useCallback(
|
||||
|
|
@ -1289,6 +1320,7 @@ export const useConnectorDialog = () => {
|
|||
setConnectorConfig(null);
|
||||
setConnectingConnectorType(null);
|
||||
setViewingAccountsType(null);
|
||||
setCameFromAccountsList(null);
|
||||
setStartDate(undefined);
|
||||
setEndDate(undefined);
|
||||
setPeriodicEnabled(false);
|
||||
|
|
|
|||
|
|
@ -71,27 +71,30 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
|
|||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
{/* Header */}
|
||||
<div className="px-4 sm:px-12 pt-6 sm:pt-10 pb-4 border-b border-border/50 bg-muted">
|
||||
<div className="flex items-center justify-between gap-4 sm:pr-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="size-8 rounded-full shrink-0"
|
||||
onClick={onBack}
|
||||
>
|
||||
<ArrowLeft className="size-4" />
|
||||
</Button>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-slate-400/5 dark:bg-white/5 border border-slate-400/5 dark:border-white/5">
|
||||
{getConnectorIcon(connectorType, "size-5")}
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold">{connectorTitle} Accounts</h2>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{typeConnectors.length} connected account{typeConnectors.length !== 1 ? "s" : ""}
|
||||
</p>
|
||||
</div>
|
||||
<div className="px-6 sm:px-12 pt-8 sm:pt-10 pb-4 border-b border-border/50 bg-muted">
|
||||
{/* Back button */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={onBack}
|
||||
className="flex items-center gap-2 text-xs sm:text-sm text-muted-foreground hover:text-foreground mb-6 w-fit"
|
||||
>
|
||||
<ArrowLeft className="size-4" />
|
||||
Back to connectors
|
||||
</button>
|
||||
|
||||
{/* Connector header */}
|
||||
<div className="flex flex-col sm:flex-row items-start sm:items-center gap-4 mb-6">
|
||||
<div className="flex gap-4 flex-1 w-full sm:w-auto">
|
||||
<div className="flex h-14 w-14 items-center justify-center rounded-xl bg-primary/10 border border-primary/20 shrink-0">
|
||||
{getConnectorIcon(connectorType, "size-7")}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h2 className="text-xl sm:text-2xl font-semibold tracking-tight text-wrap whitespace-normal">
|
||||
{connectorTitle}
|
||||
</h2>
|
||||
<p className="text-xs sm:text-base text-muted-foreground mt-1">
|
||||
Manage your connector settings and sync configuration
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{/* Add Account Button with dashed border */}
|
||||
|
|
@ -100,7 +103,7 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
|
|||
onClick={onAddAccount}
|
||||
disabled={isConnecting}
|
||||
className={cn(
|
||||
"flex items-center gap-2 px-3 py-2 rounded-lg mr-4 border-2 border-dashed border-border/70 text-left transition-all duration-200",
|
||||
"flex items-center gap-2 px-3 py-2 rounded-lg border-2 border-dashed border-border/70 text-left transition-all duration-200 shrink-0 self-center sm:self-auto sm:w-auto",
|
||||
"border-primary/50 hover:bg-primary/5",
|
||||
isConnecting && "opacity-50 cursor-not-allowed"
|
||||
)}
|
||||
|
|
@ -120,7 +123,7 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
|
|||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1 overflow-y-auto px-4 sm:px-12 py-6 sm:py-8">
|
||||
<div className="flex-1 overflow-y-auto px-6 sm:px-12 py-6 sm:py-8">
|
||||
{/* Connected Accounts Grid */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
{typeConnectors.map((connector) => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue