[#271] abort truly goes through when removing user in cal search (#295)

Co-authored-by: Camille Moussu <cmoussu@linagora.com>
This commit is contained in:
Camille Moussu
2025-11-13 09:24:34 +01:00
committed by GitHub
parent 3c26a81773
commit 85ccf8e14d
6 changed files with 114 additions and 35 deletions
@@ -330,7 +330,15 @@ describe("CalendarSlice", () => {
it("getCalendarDetailAsync.fulfilled adds calendar events", () => { it("getCalendarDetailAsync.fulfilled adds calendar events", () => {
const payload = { calId: "c1", events: [{ uid: "e1" }] as any[] }; const payload = { calId: "c1", events: [{ uid: "e1" }] as any[] };
const state = reducer( const state = reducer(
initialState, {
...initialState,
list: {
["c1"]: {
id: "c1",
events: {},
} as unknown as Calendars,
},
},
getCalendarDetailAsync.fulfilled(payload, "req11", { getCalendarDetailAsync.fulfilled(payload, "req11", {
calId: "c1", calId: "c1",
match: { start: "", end: "" }, match: { start: "", end: "" },
+17 -1
View File
@@ -211,6 +211,10 @@ export default function CalendarApp({
const [prevTempCalendars, setPrevTempCalendars] = useState<string[]>([]); const [prevTempCalendars, setPrevTempCalendars] = useState<string[]>([]);
const [prevRangeKey, setPrevRangeKey] = useState<string>(""); const [prevRangeKey, setPrevRangeKey] = useState<string>("");
const tempCalendarControllersRef = useRef<Map<string, AbortController>>(
new Map()
);
useEffect(() => { useEffect(() => {
updateCalsDetails( updateCalsDetails(
Object.keys(tempcalendars), Object.keys(tempcalendars),
@@ -220,9 +224,21 @@ export default function CalendarApp({
prevRangeKey, prevRangeKey,
dispatch, dispatch,
calendarRange, calendarRange,
"temp" "temp",
tempCalendarControllersRef.current
); );
prevTempCalendars.forEach((calId) => {
if (!Object.keys(tempcalendars).includes(calId)) {
const controller = tempCalendarControllersRef.current.get(calId);
if (controller) {
controller.abort();
tempCalendarControllersRef.current.delete(calId);
}
delete fetchedRangesRef.current[calId];
}
});
setPrevTempCalendars(Object.keys(tempcalendars)); setPrevTempCalendars(Object.keys(tempcalendars));
setPrevRangeKey(rangeKey); setPrevRangeKey(rangeKey);
}, [rangeKey, Object.keys(tempcalendars).join(","), pending]); }, [rangeKey, Object.keys(tempcalendars).join(","), pending]);
+56 -21
View File
@@ -107,7 +107,8 @@ export const updateCalsDetails = (
previousRangeKey: string, previousRangeKey: string,
dispatch: Function, dispatch: Function,
calendarRange: { start: Date; end: Date }, calendarRange: { start: Date; end: Date },
calType?: "temp" calType?: "temp",
controllers?: Map<string, AbortController>
) => { ) => {
if (pending || !rangeKey) return; if (pending || !rangeKey) return;
@@ -116,31 +117,65 @@ export const updateCalsDetails = (
); );
newCalendars.forEach((id) => { newCalendars.forEach((id) => {
dispatch( if (controllers) {
getCalendarDetailAsync({ const controller = new AbortController();
calId: id, controllers.set(id, controller);
match: {
start: formatDateToYYYYMMDDTHHMMSS(calendarRange.start), dispatch(
end: formatDateToYYYYMMDDTHHMMSS(calendarRange.end), getCalendarDetailAsync({
}, calId: id,
calType, match: {
}) start: formatDateToYYYYMMDDTHHMMSS(calendarRange.start),
); end: formatDateToYYYYMMDDTHHMMSS(calendarRange.end),
},
calType,
signal: controller.signal,
})
);
} else {
dispatch(
getCalendarDetailAsync({
calId: id,
match: {
start: formatDateToYYYYMMDDTHHMMSS(calendarRange.start),
end: formatDateToYYYYMMDDTHHMMSS(calendarRange.end),
},
calType,
})
);
}
}); });
if (rangeKey !== previousRangeKey) { if (rangeKey !== previousRangeKey) {
selectedCalendars?.forEach((id) => { selectedCalendars?.forEach((id) => {
if (id) { if (id) {
dispatch( if (controllers) {
getCalendarDetailAsync({ const controller = new AbortController();
calId: id, controllers.set(id, controller);
match: {
start: formatDateToYYYYMMDDTHHMMSS(calendarRange.start), dispatch(
end: formatDateToYYYYMMDDTHHMMSS(calendarRange.end), getCalendarDetailAsync({
}, calId: id,
calType, match: {
}) start: formatDateToYYYYMMDDTHHMMSS(calendarRange.start),
); end: formatDateToYYYYMMDDTHHMMSS(calendarRange.end),
},
calType,
signal: controller.signal,
})
);
} else {
dispatch(
getCalendarDetailAsync({
calId: id,
match: {
start: formatDateToYYYYMMDDTHHMMSS(calendarRange.start),
end: formatDateToYYYYMMDDTHHMMSS(calendarRange.end),
},
calType,
})
);
}
} }
}); });
} }
+6 -2
View File
@@ -2,13 +2,15 @@ import { api } from "../../utils/apiUtils";
export async function getCalendars( export async function getCalendars(
userId: string, userId: string,
scope: string = "personal=true&sharedDelegationStatus=accepted&sharedPublicSubscription=true&withRights=true" scope: string = "personal=true&sharedDelegationStatus=accepted&sharedPublicSubscription=true&withRights=true",
signal?: AbortSignal
) { ) {
const calendars = await api const calendars = await api
.get(`dav/calendars/${userId}.json?${scope}`, { .get(`dav/calendars/${userId}.json?${scope}`, {
headers: { headers: {
Accept: "application/calendar+json", Accept: "application/calendar+json",
}, },
signal,
}) })
.json(); .json();
return calendars; return calendars;
@@ -16,7 +18,8 @@ export async function getCalendars(
export async function getCalendar( export async function getCalendar(
id: string, id: string,
match: { start: string; end: string } match: { start: string; end: string },
signal?: AbortSignal
) { ) {
const response = await api(`dav/calendars/${id}.json`, { const response = await api(`dav/calendars/${id}.json`, {
method: "REPORT", method: "REPORT",
@@ -26,6 +29,7 @@ export async function getCalendar(
body: JSON.stringify({ body: JSON.stringify({
match, match,
}), }),
signal,
}); });
const calendar = await response.json(); const calendar = await response.json();
return calendar; return calendar;
+24 -7
View File
@@ -171,13 +171,21 @@ export const getTempCalendarsListAsync = createAsyncThunk<
export const getCalendarDetailAsync = createAsyncThunk< export const getCalendarDetailAsync = createAsyncThunk<
{ calId: string; events: CalendarEvent[]; calType?: string }, { calId: string; events: CalendarEvent[]; calType?: string },
{ calId: string; match: { start: string; end: string }; calType?: string }, {
calId: string;
match: { start: string; end: string };
calType?: string;
signal?: AbortSignal;
},
{ rejectValue: RejectedError } { rejectValue: RejectedError }
>( >(
"calendars/getCalendarDetails", "calendars/getCalendarDetails",
async ({ calId, match, calType }, { rejectWithValue }) => { async ({ calId, match, calType, signal }, { rejectWithValue }) => {
try { try {
const calendar = (await getCalendar(calId, match)) as Record<string, any>; const calendar = (await getCalendar(calId, match, signal)) as Record<
string,
any
>;
const color = calendar["apple:color"]; const color = calendar["apple:color"];
const events: CalendarEvent[] = calendar._embedded["dav:item"].flatMap( const events: CalendarEvent[] = calendar._embedded["dav:item"].flatMap(
(eventdata: any) => { (eventdata: any) => {
@@ -736,10 +744,7 @@ const CalendarSlice = createSlice({
const type = action.payload.calType === "temp" ? "templist" : "list"; const type = action.payload.calType === "temp" ? "templist" : "list";
if (!state[type][action.payload.calId]) { if (!state[type][action.payload.calId]) {
state[type][action.payload.calId] = { return;
id: action.payload.calId,
events: {},
} as Calendars;
} }
action.payload.events.forEach((event) => { action.payload.events.forEach((event) => {
state[type][action.payload.calId].events[event.uid] = event; state[type][action.payload.calId].events[event.uid] = event;
@@ -998,6 +1003,12 @@ const CalendarSlice = createSlice({
}) })
.addCase(getTempCalendarsListAsync.rejected, (state, action) => { .addCase(getTempCalendarsListAsync.rejected, (state, action) => {
state.pending = false; state.pending = false;
if (
action.payload?.message.includes("aborted") ||
action.error.name === "AbortError"
) {
return;
}
state.error = state.error =
action.payload?.message || action.payload?.message ||
action.error.message || action.error.message ||
@@ -1005,6 +1016,12 @@ const CalendarSlice = createSlice({
}) })
.addCase(getCalendarDetailAsync.rejected, (state, action) => { .addCase(getCalendarDetailAsync.rejected, (state, action) => {
state.pending = false; state.pending = false;
if (
action.payload?.message.includes("aborted") ||
action.error.name === "AbortError"
) {
return;
}
state.error = state.error =
action.payload?.message || action.payload?.message ||
action.error.message || action.error.message ||
+2 -3
View File
@@ -11,9 +11,8 @@ export const api = ky.extend({
: null; : null;
const access_token = saved?.access_token; const access_token = saved?.access_token;
const modifiedRequest = new Request(request); request.headers.set("Authorization", `Bearer ${access_token}`);
modifiedRequest.headers.set("Authorization", `Bearer ${access_token}`); return request;
return modifiedRequest;
}, },
], ],
afterResponse: [ afterResponse: [