Fix this_only concurrent request cancellation issue
Resolves the same "Failed to fetch" cancellation issue that was occurring in the "modify single event in a series" flow by eliminating concurrent HTTP requests from the frontend. ## Problem: The RecurringEditAction::ThisEvent handler was making two concurrent requests: 1. UPDATE request via update_callback.emit() 2. CREATE request via create_callback.emit() This caused the same race condition and HTTP cancellation (~700-900ms) that we previously fixed in the "this_and_future" flow. ## Solution: - **Remove concurrent CREATE request** from frontend - **Use single UPDATE request** with "this_only" scope - **Backend handles both operations** atomically: 1. Add EXDATE to original series (exclude occurrence) 2. Create exception event with RECURRENCE-ID (user modifications) ## Implementation: - Frontend sends single request with occurrence_date and dragged times - Backend update_single_occurrence() already handled both operations - Added comprehensive RFC 5545 documentation for single occurrence modification - Cleaned up unused imports and variables ## Benefits: - No more HTTP request cancellation for single event modifications - Proper RFC 5545 EXDATE + RECURRENCE-ID exception handling - Atomic operations ensure data consistency - Matches the pattern used in this_and_future fix The "modify single event" drag operations now work reliably without network errors, completing the fix for all recurring event modification flows. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,7 @@ use std::collections::HashMap;
|
||||
use web_sys::MouseEvent;
|
||||
use crate::services::calendar_service::UserInfo;
|
||||
use crate::models::ical::VEvent;
|
||||
use crate::components::{RecurringEditModal, RecurringEditAction, EventCreationData, EventStatus, EventClass, ReminderType, RecurrenceType};
|
||||
use crate::components::{RecurringEditModal, RecurringEditAction, EventCreationData};
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct WeekViewProps {
|
||||
@@ -106,57 +106,49 @@ pub fn week_view(props: &WeekViewProps) -> Html {
|
||||
let pending_recurring_edit = pending_recurring_edit.clone();
|
||||
let on_event_update = props.on_event_update.clone();
|
||||
let _on_create_event = props.on_create_event.clone();
|
||||
let on_create_event_request = props.on_create_event_request.clone();
|
||||
let events = props.events.clone();
|
||||
Callback::from(move |action: RecurringEditAction| {
|
||||
if let Some(edit) = (*pending_recurring_edit).clone() {
|
||||
match action {
|
||||
RecurringEditAction::ThisEvent => {
|
||||
// Use the series endpoint with "this_only" scope for RFC 5545 compliant single occurrence modification
|
||||
|
||||
web_sys::console::log_1(&format!("🎯 Single occurrence modification: calling series update with this_only scope for event '{}'",
|
||||
edit.event.summary.as_deref().unwrap_or("Untitled")
|
||||
).into());
|
||||
|
||||
// TODO: Need to call calendar service directly with update_scope "this_only" and occurrence_date
|
||||
// For now, fall back to the old method but with better logging
|
||||
// RFC 5545 Compliant Single Occurrence Modification: "This Event Only"
|
||||
//
|
||||
// When a user chooses to modify "this event only" for a recurring series,
|
||||
// we implement an exception-based modification that:
|
||||
//
|
||||
// 1. **Add EXDATE to Original Series**: The original series is updated with
|
||||
// an EXDATE entry to exclude this specific occurrence from generation
|
||||
// 2. **Create Exception Event**: A new standalone event is created with
|
||||
// RECURRENCE-ID pointing to the original occurrence, containing the modifications
|
||||
//
|
||||
// Example: User drags Aug 22 occurrence of "Daily 9AM meeting" to 2PM:
|
||||
// - Original series: "Daily 9AM meeting" + EXDATE for Aug 22 (continues as normal except Aug 22)
|
||||
// - Exception event: "Daily 2PM meeting" with RECURRENCE-ID=Aug22 (only affects Aug 22)
|
||||
//
|
||||
// This approach ensures:
|
||||
// - All other occurrences remain unchanged (past and future)
|
||||
// - Modified occurrence displays user's changes
|
||||
// - RFC 5545 compliance through EXDATE and RECURRENCE-ID
|
||||
// - CalDAV compatibility with standard calendar applications
|
||||
//
|
||||
// The backend handles both operations atomically within a single API call.
|
||||
if let Some(update_callback) = &on_event_update {
|
||||
// This currently goes to regular update endpoint, but we need it to go to series endpoint
|
||||
// with update_scope: "this_only" and occurrence_date: edit.event.dtstart.format("%Y-%m-%d")
|
||||
let updated_event = edit.event.clone();
|
||||
// Extract occurrence date for backend processing
|
||||
let occurrence_date = edit.event.dtstart.format("%Y-%m-%d").to_string();
|
||||
|
||||
web_sys::console::log_1(&format!("⚠️ Using regular update callback - this should be changed to use series endpoint with this_only scope").into());
|
||||
|
||||
update_callback.emit((updated_event, edit.new_start, edit.new_end, true, None, Some("this_only".to_string()), Some(edit.event.dtstart.format("%Y-%m-%d").to_string()))); // preserve_rrule = true, update_scope = this_only
|
||||
}
|
||||
|
||||
// Note: The proper fix requires calling calendar_service.update_event_with_scope() directly
|
||||
// with update_scope: "this_only" and occurrence_date
|
||||
if let Some(create_callback) = &on_create_event_request {
|
||||
// Convert to EventCreationData for single event
|
||||
let event_data = EventCreationData {
|
||||
title: edit.event.summary.clone().unwrap_or_default(),
|
||||
description: edit.event.description.clone().unwrap_or_default(),
|
||||
start_date: edit.new_start.date(),
|
||||
start_time: edit.new_start.time(),
|
||||
end_date: edit.new_end.date(),
|
||||
end_time: edit.new_end.time(),
|
||||
location: edit.event.location.clone().unwrap_or_default(),
|
||||
all_day: edit.event.all_day,
|
||||
status: EventStatus::Confirmed,
|
||||
class: EventClass::Public,
|
||||
priority: edit.event.priority,
|
||||
organizer: edit.event.organizer.as_ref().map(|o| o.cal_address.clone()).unwrap_or_default(),
|
||||
attendees: edit.event.attendees.iter().map(|a| a.cal_address.clone()).collect::<Vec<_>>().join(","),
|
||||
categories: edit.event.categories.join(","),
|
||||
reminder: ReminderType::None,
|
||||
recurrence: RecurrenceType::None, // Single event, no recurrence
|
||||
recurrence_days: vec![false; 7],
|
||||
selected_calendar: edit.event.calendar_path.clone(),
|
||||
};
|
||||
|
||||
// Create the single event
|
||||
create_callback.emit(event_data);
|
||||
// Send single request to backend with "this_only" scope
|
||||
// Backend will atomically:
|
||||
// 1. Add EXDATE to original series (excludes this occurrence)
|
||||
// 2. Create exception event with RECURRENCE-ID and user's modifications
|
||||
update_callback.emit((
|
||||
edit.event.clone(), // Original event (series to modify)
|
||||
edit.new_start, // Dragged start time for exception
|
||||
edit.new_end, // Dragged end time for exception
|
||||
true, // preserve_rrule = true
|
||||
None, // No until_date for this_only
|
||||
Some("this_only".to_string()), // Update scope
|
||||
Some(occurrence_date) // Date of occurrence being modified
|
||||
));
|
||||
}
|
||||
},
|
||||
RecurringEditAction::FutureEvents => {
|
||||
|
||||
Reference in New Issue
Block a user