|
5 | 5 | */ |
6 | 6 |
|
7 | 7 | import { useEffect, useRef, useState } from "react"; |
8 | | -import { xor } from "lodash-es"; |
| 8 | +import { isEqual, xor } from "lodash-es"; |
9 | 9 | import { observer } from "mobx-react"; |
10 | 10 | import { useParams } from "next/navigation"; |
11 | 11 | // Plane imports |
@@ -260,48 +260,78 @@ export const CreateUpdateIssueModalBase = observer(function CreateUpdateIssueMod |
260 | 260 | } |
261 | 261 | }; |
262 | 262 |
|
| 263 | + const handleCycleChange = async (data: Partial<TIssue> | undefined, payload: Partial<TIssue>) => { |
| 264 | + if (!workspaceSlug || !data?.project_id || !data?.id) return; |
| 265 | + // return if user is not trying to change the cycle, i.e |
| 266 | + // - cycle_id is not present in payload |
| 267 | + // - cycle_id is the same as the current cycle id |
| 268 | + if (!("cycle_id" in payload) || isEqual(data?.cycle_id, payload.cycle_id)) return; |
| 269 | + |
| 270 | + const slug = workspaceSlug.toString(); |
| 271 | + |
| 272 | + // Removing the cycle |
| 273 | + const currentCycleId = data?.cycle_id; |
| 274 | + if (currentCycleId && payload.cycle_id === null) { |
| 275 | + await issues.removeIssueFromCycle(slug, data.project_id, currentCycleId, data.id); |
| 276 | + fetchCycleDetails(slug, data.project_id, currentCycleId).catch((error) => { |
| 277 | + console.error(error); |
| 278 | + }); |
| 279 | + } |
| 280 | + |
| 281 | + // Adding the cycle |
| 282 | + const newCycleId = payload.cycle_id; |
| 283 | + if (newCycleId && newCycleId !== "" && (payload.cycle_id !== cycleId || storeType !== EIssuesStoreType.CYCLE)) { |
| 284 | + await addIssueToCycle(data as TBaseIssue, newCycleId); |
| 285 | + } |
| 286 | + }; |
| 287 | + |
| 288 | + const handleModuleChange = async (data: Partial<TIssue>, payload: Partial<TIssue>) => { |
| 289 | + if (!workspaceSlug || !data?.project_id || !data?.id) return; |
| 290 | + // return if user is not trying to change the module, i.e |
| 291 | + // - module_ids is not present in payload |
| 292 | + // - module_ids is not an array |
| 293 | + // - module_ids is the same as the current module ids |
| 294 | + if ( |
| 295 | + !("module_ids" in payload) || |
| 296 | + !Array.isArray(payload.module_ids) || |
| 297 | + isEqual(data?.module_ids, payload.module_ids) |
| 298 | + ) |
| 299 | + return; |
| 300 | + |
| 301 | + const updatedModuleIds = xor(data.module_ids, payload.module_ids); |
| 302 | + const modulesToAdd: string[] = []; |
| 303 | + const modulesToRemove: string[] = []; |
| 304 | + |
| 305 | + for (const moduleId of updatedModuleIds) { |
| 306 | + if (data.module_ids?.includes(moduleId)) { |
| 307 | + modulesToRemove.push(moduleId); |
| 308 | + } else { |
| 309 | + modulesToAdd.push(moduleId); |
| 310 | + } |
| 311 | + } |
| 312 | + // update modules if there are modules to add or remove |
| 313 | + if (modulesToAdd.length > 0 || modulesToRemove.length > 0) { |
| 314 | + await issues.changeModulesInIssue( |
| 315 | + workspaceSlug.toString(), |
| 316 | + data.project_id, |
| 317 | + data.id, |
| 318 | + modulesToAdd, |
| 319 | + modulesToRemove |
| 320 | + ); |
| 321 | + } |
| 322 | + }; |
| 323 | + |
263 | 324 | const handleUpdateIssue = async (payload: Partial<TIssue>): Promise<TIssue | undefined> => { |
264 | 325 | if (!workspaceSlug || !payload.project_id || !data?.id) return; |
265 | 326 |
|
266 | 327 | try { |
267 | 328 | if (isDraft) await draftIssues.updateIssue(workspaceSlug.toString(), data.id, payload); |
268 | 329 | else if (updateIssue) await updateIssue(payload.project_id, data.id, payload); |
269 | 330 |
|
270 | | - // check if we should add/remove issue to/from cycle |
271 | | - if ( |
272 | | - payload.cycle_id && |
273 | | - payload.cycle_id !== "" && |
274 | | - (payload.cycle_id !== cycleId || storeType !== EIssuesStoreType.CYCLE) |
275 | | - ) { |
276 | | - await addIssueToCycle(data as TBaseIssue, payload.cycle_id); |
277 | | - } |
278 | | - if (data.cycle_id && !payload.cycle_id && data.project_id) { |
279 | | - await issues.removeIssueFromCycle(workspaceSlug.toString(), data.project_id, data.cycle_id, data.id); |
280 | | - fetchCycleDetails(workspaceSlug.toString(), data.project_id, data.cycle_id); |
281 | | - } |
282 | | - |
283 | | - if (data.module_ids && payload.module_ids && data.project_id) { |
284 | | - const updatedModuleIds = xor(data.module_ids, payload.module_ids); |
285 | | - const modulesToAdd: string[] = []; |
286 | | - const modulesToRemove: string[] = []; |
287 | | - |
288 | | - for (const moduleId of updatedModuleIds) { |
289 | | - if (data.module_ids.includes(moduleId)) { |
290 | | - modulesToRemove.push(moduleId); |
291 | | - } else { |
292 | | - modulesToAdd.push(moduleId); |
293 | | - } |
294 | | - } |
295 | | - await issues.changeModulesInIssue( |
296 | | - workspaceSlug.toString(), |
297 | | - data.project_id, |
298 | | - data.id, |
299 | | - modulesToAdd, |
300 | | - modulesToRemove |
301 | | - ); |
302 | | - } |
303 | | - |
304 | | - // add other property values |
| 331 | + // Run cycle, module, and property changes sequentially to avoid |
| 332 | + // optimistic store writes from racing against each other. |
| 333 | + await handleCycleChange(data, payload); |
| 334 | + await handleModuleChange(data, payload); |
305 | 335 | await handleCreateUpdatePropertyValues({ |
306 | 336 | issueId: data.id, |
307 | 337 | issueTypeId: payload.type_id, |
|
0 commit comments