@critical-path/react 0.13.11 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/README.md +5 -0
- package/dist/cancellation.test.d.ts +2 -0
- package/dist/cancellation.test.d.ts.map +1 -0
- package/dist/cancellation.test.js +42 -0
- package/dist/cancellation.test.js.map +1 -0
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +213 -72
- package/dist/hooks.js.map +1 -1
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +1 -0
- package/dist/provider.js.map +1 -1
- package/package.json +8 -4
- package/src/cancellation.test.tsx +58 -0
- package/src/hooks.ts +172 -72
- package/src/provider.tsx +2 -0
- package/tsconfig.tsbuildinfo +1 -1
package/dist/hooks.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
import { useState, useEffect, useCallback, useMemo, useRef } from 'react';
|
|
2
3
|
import { resolveStatusDefinition, isTempTaskId } from '@critical-path/core';
|
|
3
4
|
import { registerWebMcpTools } from '@critical-path/mcp/web';
|
|
@@ -7,22 +8,32 @@ export function useWorkflows() {
|
|
|
7
8
|
const [workflows, setWorkflows] = useState([]);
|
|
8
9
|
const [loading, setLoading] = useState(true);
|
|
9
10
|
const [error, setError] = useState(null);
|
|
10
|
-
const fetchWorkflows = useCallback(async () => {
|
|
11
|
+
const fetchWorkflows = useCallback(async (signal) => {
|
|
12
|
+
const api = signal ? client.with({ signal }) : client;
|
|
11
13
|
try {
|
|
12
14
|
setLoading(true);
|
|
13
|
-
const data = await
|
|
15
|
+
const data = await api.getWorkflows();
|
|
16
|
+
if (signal?.aborted)
|
|
17
|
+
return;
|
|
14
18
|
setWorkflows(data);
|
|
15
19
|
setError(null);
|
|
16
20
|
}
|
|
17
21
|
catch (err) {
|
|
22
|
+
if (signal?.aborted)
|
|
23
|
+
return;
|
|
18
24
|
setError(err instanceof Error ? err : new Error(String(err)));
|
|
19
25
|
}
|
|
20
26
|
finally {
|
|
21
|
-
|
|
27
|
+
if (!signal?.aborted)
|
|
28
|
+
setLoading(false);
|
|
22
29
|
}
|
|
23
30
|
}, [client]);
|
|
24
31
|
useEffect(() => {
|
|
25
|
-
|
|
32
|
+
// Abort the in-flight request when inputs change or the component unmounts, so a slow
|
|
33
|
+
// response for old inputs can never overwrite newer data.
|
|
34
|
+
const controller = new AbortController();
|
|
35
|
+
fetchWorkflows(controller.signal);
|
|
36
|
+
return () => controller.abort();
|
|
26
37
|
}, [fetchWorkflows]);
|
|
27
38
|
const createWorkflow = async (input) => {
|
|
28
39
|
try {
|
|
@@ -59,57 +70,77 @@ export function useWorkflows() {
|
|
|
59
70
|
throw errorObj;
|
|
60
71
|
}
|
|
61
72
|
};
|
|
62
|
-
return { workflows, loading, error, refresh: fetchWorkflows, createWorkflow, updateWorkflow, deleteWorkflow };
|
|
73
|
+
return { workflows, loading, error, refresh: () => fetchWorkflows(), createWorkflow, updateWorkflow, deleteWorkflow };
|
|
63
74
|
}
|
|
64
75
|
export function useTaskTransitions(taskId) {
|
|
65
76
|
const client = useCriticalPathClient();
|
|
66
77
|
const [allowedTransitions, setAllowedTransitions] = useState([]);
|
|
67
78
|
const [loading, setLoading] = useState(false);
|
|
68
79
|
const [error, setError] = useState(null);
|
|
69
|
-
const fetchTransitions = useCallback(async () => {
|
|
80
|
+
const fetchTransitions = useCallback(async (signal) => {
|
|
81
|
+
const api = signal ? client.with({ signal }) : client;
|
|
70
82
|
if (!taskId) {
|
|
71
83
|
setAllowedTransitions([]);
|
|
72
84
|
return;
|
|
73
85
|
}
|
|
74
86
|
try {
|
|
75
87
|
setLoading(true);
|
|
76
|
-
const data = await
|
|
88
|
+
const data = await api.getAllowedTaskTransitions(taskId);
|
|
89
|
+
if (signal?.aborted)
|
|
90
|
+
return;
|
|
77
91
|
setAllowedTransitions(data);
|
|
78
92
|
setError(null);
|
|
79
93
|
}
|
|
80
94
|
catch (err) {
|
|
95
|
+
if (signal?.aborted)
|
|
96
|
+
return;
|
|
81
97
|
setError(err instanceof Error ? err : new Error(String(err)));
|
|
82
98
|
}
|
|
83
99
|
finally {
|
|
84
|
-
|
|
100
|
+
if (!signal?.aborted)
|
|
101
|
+
setLoading(false);
|
|
85
102
|
}
|
|
86
103
|
}, [client, taskId]);
|
|
87
104
|
useEffect(() => {
|
|
88
|
-
|
|
105
|
+
// Abort the in-flight request when inputs change or the component unmounts, so a slow
|
|
106
|
+
// response for old inputs can never overwrite newer data.
|
|
107
|
+
const controller = new AbortController();
|
|
108
|
+
fetchTransitions(controller.signal);
|
|
109
|
+
return () => controller.abort();
|
|
89
110
|
}, [fetchTransitions]);
|
|
90
|
-
return { allowedTransitions, loading, error, refresh: fetchTransitions };
|
|
111
|
+
return { allowedTransitions, loading, error, refresh: () => fetchTransitions() };
|
|
91
112
|
}
|
|
92
113
|
export function useProjects() {
|
|
93
114
|
const client = useCriticalPathClient();
|
|
94
115
|
const [projects, setProjects] = useState([]);
|
|
95
116
|
const [loading, setLoading] = useState(true);
|
|
96
117
|
const [error, setError] = useState(null);
|
|
97
|
-
const fetchProjects = useCallback(async () => {
|
|
118
|
+
const fetchProjects = useCallback(async (signal) => {
|
|
119
|
+
const api = signal ? client.with({ signal }) : client;
|
|
98
120
|
try {
|
|
99
121
|
setLoading(true);
|
|
100
|
-
const data = await
|
|
122
|
+
const data = await api.getProjects();
|
|
123
|
+
if (signal?.aborted)
|
|
124
|
+
return;
|
|
101
125
|
setProjects(data);
|
|
102
126
|
setError(null);
|
|
103
127
|
}
|
|
104
128
|
catch (err) {
|
|
129
|
+
if (signal?.aborted)
|
|
130
|
+
return;
|
|
105
131
|
setError(err instanceof Error ? err : new Error(String(err)));
|
|
106
132
|
}
|
|
107
133
|
finally {
|
|
108
|
-
|
|
134
|
+
if (!signal?.aborted)
|
|
135
|
+
setLoading(false);
|
|
109
136
|
}
|
|
110
137
|
}, [client]);
|
|
111
138
|
useEffect(() => {
|
|
112
|
-
|
|
139
|
+
// Abort the in-flight request when inputs change or the component unmounts, so a slow
|
|
140
|
+
// response for old inputs can never overwrite newer data.
|
|
141
|
+
const controller = new AbortController();
|
|
142
|
+
fetchProjects(controller.signal);
|
|
143
|
+
return () => controller.abort();
|
|
113
144
|
}, [fetchProjects]);
|
|
114
145
|
const createProject = async (input) => {
|
|
115
146
|
const tempId = `temp_${Date.now()}_${Math.random().toString(36).substring(2, 7)}`;
|
|
@@ -133,31 +164,41 @@ export function useProjects() {
|
|
|
133
164
|
throw errorObj;
|
|
134
165
|
}
|
|
135
166
|
};
|
|
136
|
-
return { projects, loading, error, refresh: fetchProjects, createProject };
|
|
167
|
+
return { projects, loading, error, refresh: () => fetchProjects(), createProject };
|
|
137
168
|
}
|
|
138
169
|
export function useTasks(projectId) {
|
|
139
170
|
const client = useCriticalPathClient();
|
|
140
171
|
const [tasks, setTasks] = useState([]);
|
|
141
172
|
const [loading, setLoading] = useState(true);
|
|
142
173
|
const [error, setError] = useState(null);
|
|
143
|
-
const fetchTasks = useCallback(async () => {
|
|
174
|
+
const fetchTasks = useCallback(async (signal) => {
|
|
175
|
+
const api = signal ? client.with({ signal }) : client;
|
|
144
176
|
try {
|
|
145
177
|
setLoading(true);
|
|
146
|
-
const data = await
|
|
178
|
+
const data = await api.getTasks(projectId);
|
|
179
|
+
if (signal?.aborted)
|
|
180
|
+
return;
|
|
147
181
|
setTasks(data);
|
|
148
182
|
setError(null);
|
|
149
183
|
}
|
|
150
184
|
catch (err) {
|
|
185
|
+
if (signal?.aborted)
|
|
186
|
+
return;
|
|
151
187
|
setError(err instanceof Error ? err : new Error(String(err)));
|
|
152
188
|
}
|
|
153
189
|
finally {
|
|
154
|
-
|
|
190
|
+
if (!signal?.aborted)
|
|
191
|
+
setLoading(false);
|
|
155
192
|
}
|
|
156
193
|
}, [client, projectId]);
|
|
157
194
|
const pendingCreationsRef = useRef(new Map());
|
|
158
195
|
const tempToRealIdMapRef = useRef(new Map());
|
|
159
196
|
useEffect(() => {
|
|
160
|
-
|
|
197
|
+
// Abort the in-flight request when inputs change or the component unmounts, so a slow
|
|
198
|
+
// response for old inputs can never overwrite newer data.
|
|
199
|
+
const controller = new AbortController();
|
|
200
|
+
fetchTasks(controller.signal);
|
|
201
|
+
return () => controller.abort();
|
|
161
202
|
}, [fetchTasks]);
|
|
162
203
|
const createTask = async (input) => {
|
|
163
204
|
const tempId = `temp_${Date.now()}_${Math.random().toString(36).substring(2, 7)}`;
|
|
@@ -298,7 +339,7 @@ export function useTasks(projectId) {
|
|
|
298
339
|
throw errorObj;
|
|
299
340
|
}
|
|
300
341
|
};
|
|
301
|
-
return { tasks, loading, error, refresh: fetchTasks, createTask, updateTask, updateTaskStatus, deleteTask };
|
|
342
|
+
return { tasks, loading, error, refresh: () => fetchTasks(), createTask, updateTask, updateTaskStatus, deleteTask };
|
|
302
343
|
}
|
|
303
344
|
export function useKanban(projectId, options) {
|
|
304
345
|
const { tasks, loading, error, refresh, updateTaskStatus, createTask } = useTasks(projectId);
|
|
@@ -353,7 +394,8 @@ export function useComments(taskId) {
|
|
|
353
394
|
const [comments, setComments] = useState([]);
|
|
354
395
|
const [loading, setLoading] = useState(true);
|
|
355
396
|
const [error, setError] = useState(null);
|
|
356
|
-
const fetchComments = useCallback(async () => {
|
|
397
|
+
const fetchComments = useCallback(async (signal) => {
|
|
398
|
+
const api = signal ? client.with({ signal }) : client;
|
|
357
399
|
if (!taskId) {
|
|
358
400
|
setComments([]);
|
|
359
401
|
setLoading(false);
|
|
@@ -361,19 +403,28 @@ export function useComments(taskId) {
|
|
|
361
403
|
}
|
|
362
404
|
try {
|
|
363
405
|
setLoading(true);
|
|
364
|
-
const data = await
|
|
406
|
+
const data = await api.getComments(taskId);
|
|
407
|
+
if (signal?.aborted)
|
|
408
|
+
return;
|
|
365
409
|
setComments(data);
|
|
366
410
|
setError(null);
|
|
367
411
|
}
|
|
368
412
|
catch (err) {
|
|
413
|
+
if (signal?.aborted)
|
|
414
|
+
return;
|
|
369
415
|
setError(err instanceof Error ? err : new Error(String(err)));
|
|
370
416
|
}
|
|
371
417
|
finally {
|
|
372
|
-
|
|
418
|
+
if (!signal?.aborted)
|
|
419
|
+
setLoading(false);
|
|
373
420
|
}
|
|
374
421
|
}, [client, taskId]);
|
|
375
422
|
useEffect(() => {
|
|
376
|
-
|
|
423
|
+
// Abort the in-flight request when inputs change or the component unmounts, so a slow
|
|
424
|
+
// response for old inputs can never overwrite newer data.
|
|
425
|
+
const controller = new AbortController();
|
|
426
|
+
fetchComments(controller.signal);
|
|
427
|
+
return () => controller.abort();
|
|
377
428
|
}, [fetchComments]);
|
|
378
429
|
const addComment = async (input) => {
|
|
379
430
|
try {
|
|
@@ -461,7 +512,7 @@ export function useComments(taskId) {
|
|
|
461
512
|
threads,
|
|
462
513
|
loading,
|
|
463
514
|
error,
|
|
464
|
-
refresh: fetchComments,
|
|
515
|
+
refresh: () => fetchComments(),
|
|
465
516
|
addComment,
|
|
466
517
|
updateComment,
|
|
467
518
|
deleteComment,
|
|
@@ -475,22 +526,32 @@ export function useAttachments(filter) {
|
|
|
475
526
|
const [loading, setLoading] = useState(true);
|
|
476
527
|
const [error, setError] = useState(null);
|
|
477
528
|
const filterKey = JSON.stringify(filter || {});
|
|
478
|
-
const fetchAttachments = useCallback(async () => {
|
|
529
|
+
const fetchAttachments = useCallback(async (signal) => {
|
|
530
|
+
const api = signal ? client.with({ signal }) : client;
|
|
479
531
|
try {
|
|
480
532
|
setLoading(true);
|
|
481
|
-
const data = await
|
|
533
|
+
const data = await api.getAttachments(filter);
|
|
534
|
+
if (signal?.aborted)
|
|
535
|
+
return;
|
|
482
536
|
setAttachments(data);
|
|
483
537
|
setError(null);
|
|
484
538
|
}
|
|
485
539
|
catch (err) {
|
|
540
|
+
if (signal?.aborted)
|
|
541
|
+
return;
|
|
486
542
|
setError(err instanceof Error ? err : new Error(String(err)));
|
|
487
543
|
}
|
|
488
544
|
finally {
|
|
489
|
-
|
|
545
|
+
if (!signal?.aborted)
|
|
546
|
+
setLoading(false);
|
|
490
547
|
}
|
|
491
548
|
}, [client, filterKey]);
|
|
492
549
|
useEffect(() => {
|
|
493
|
-
|
|
550
|
+
// Abort the in-flight request when inputs change or the component unmounts, so a slow
|
|
551
|
+
// response for old inputs can never overwrite newer data.
|
|
552
|
+
const controller = new AbortController();
|
|
553
|
+
fetchAttachments(controller.signal);
|
|
554
|
+
return () => controller.abort();
|
|
494
555
|
}, [fetchAttachments]);
|
|
495
556
|
const createAttachment = async (input) => {
|
|
496
557
|
try {
|
|
@@ -519,7 +580,7 @@ export function useAttachments(filter) {
|
|
|
519
580
|
attachments,
|
|
520
581
|
loading,
|
|
521
582
|
error,
|
|
522
|
-
refresh: fetchAttachments,
|
|
583
|
+
refresh: () => fetchAttachments(),
|
|
523
584
|
createAttachment,
|
|
524
585
|
deleteAttachment
|
|
525
586
|
};
|
|
@@ -529,7 +590,8 @@ export function useDeliverables(projectId) {
|
|
|
529
590
|
const [deliverables, setDeliverables] = useState([]);
|
|
530
591
|
const [loading, setLoading] = useState(true);
|
|
531
592
|
const [error, setError] = useState(null);
|
|
532
|
-
const fetchDeliverables = useCallback(async () => {
|
|
593
|
+
const fetchDeliverables = useCallback(async (signal) => {
|
|
594
|
+
const api = signal ? client.with({ signal }) : client;
|
|
533
595
|
if (!projectId) {
|
|
534
596
|
setDeliverables([]);
|
|
535
597
|
setLoading(false);
|
|
@@ -537,19 +599,28 @@ export function useDeliverables(projectId) {
|
|
|
537
599
|
}
|
|
538
600
|
try {
|
|
539
601
|
setLoading(true);
|
|
540
|
-
const data = await
|
|
602
|
+
const data = await api.getDeliverables(projectId);
|
|
603
|
+
if (signal?.aborted)
|
|
604
|
+
return;
|
|
541
605
|
setDeliverables(data);
|
|
542
606
|
setError(null);
|
|
543
607
|
}
|
|
544
608
|
catch (err) {
|
|
609
|
+
if (signal?.aborted)
|
|
610
|
+
return;
|
|
545
611
|
setError(err instanceof Error ? err : new Error(String(err)));
|
|
546
612
|
}
|
|
547
613
|
finally {
|
|
548
|
-
|
|
614
|
+
if (!signal?.aborted)
|
|
615
|
+
setLoading(false);
|
|
549
616
|
}
|
|
550
617
|
}, [client, projectId]);
|
|
551
618
|
useEffect(() => {
|
|
552
|
-
|
|
619
|
+
// Abort the in-flight request when inputs change or the component unmounts, so a slow
|
|
620
|
+
// response for old inputs can never overwrite newer data.
|
|
621
|
+
const controller = new AbortController();
|
|
622
|
+
fetchDeliverables(controller.signal);
|
|
623
|
+
return () => controller.abort();
|
|
553
624
|
}, [fetchDeliverables]);
|
|
554
625
|
const createDeliverable = async (input) => {
|
|
555
626
|
try {
|
|
@@ -590,7 +661,7 @@ export function useDeliverables(projectId) {
|
|
|
590
661
|
deliverables,
|
|
591
662
|
loading,
|
|
592
663
|
error,
|
|
593
|
-
refresh: fetchDeliverables,
|
|
664
|
+
refresh: () => fetchDeliverables(),
|
|
594
665
|
createDeliverable,
|
|
595
666
|
updateDeliverable,
|
|
596
667
|
deleteDeliverable
|
|
@@ -601,7 +672,8 @@ export function useDeliverableSummary(deliverableId) {
|
|
|
601
672
|
const [summary, setSummary] = useState(null);
|
|
602
673
|
const [loading, setLoading] = useState(true);
|
|
603
674
|
const [error, setError] = useState(null);
|
|
604
|
-
const fetchSummary = useCallback(async () => {
|
|
675
|
+
const fetchSummary = useCallback(async (signal) => {
|
|
676
|
+
const api = signal ? client.with({ signal }) : client;
|
|
605
677
|
if (!deliverableId) {
|
|
606
678
|
setSummary(null);
|
|
607
679
|
setLoading(false);
|
|
@@ -609,25 +681,34 @@ export function useDeliverableSummary(deliverableId) {
|
|
|
609
681
|
}
|
|
610
682
|
try {
|
|
611
683
|
setLoading(true);
|
|
612
|
-
const data = await
|
|
684
|
+
const data = await api.getDeliverableSummary(deliverableId);
|
|
685
|
+
if (signal?.aborted)
|
|
686
|
+
return;
|
|
613
687
|
setSummary(data);
|
|
614
688
|
setError(null);
|
|
615
689
|
}
|
|
616
690
|
catch (err) {
|
|
691
|
+
if (signal?.aborted)
|
|
692
|
+
return;
|
|
617
693
|
setError(err instanceof Error ? err : new Error(String(err)));
|
|
618
694
|
}
|
|
619
695
|
finally {
|
|
620
|
-
|
|
696
|
+
if (!signal?.aborted)
|
|
697
|
+
setLoading(false);
|
|
621
698
|
}
|
|
622
699
|
}, [client, deliverableId]);
|
|
623
700
|
useEffect(() => {
|
|
624
|
-
|
|
701
|
+
// Abort the in-flight request when inputs change or the component unmounts, so a slow
|
|
702
|
+
// response for old inputs can never overwrite newer data.
|
|
703
|
+
const controller = new AbortController();
|
|
704
|
+
fetchSummary(controller.signal);
|
|
705
|
+
return () => controller.abort();
|
|
625
706
|
}, [fetchSummary]);
|
|
626
707
|
return {
|
|
627
708
|
summary,
|
|
628
709
|
loading,
|
|
629
710
|
error,
|
|
630
|
-
refresh: fetchSummary
|
|
711
|
+
refresh: () => fetchSummary()
|
|
631
712
|
};
|
|
632
713
|
}
|
|
633
714
|
export function useWebMCP(options = {}) {
|
|
@@ -675,7 +756,8 @@ export function useTaskActivity(taskId) {
|
|
|
675
756
|
const [attachments, setAttachments] = useState([]);
|
|
676
757
|
const [loading, setLoading] = useState(false);
|
|
677
758
|
const [error, setError] = useState(null);
|
|
678
|
-
const fetchActivity = useCallback(async () => {
|
|
759
|
+
const fetchActivity = useCallback(async (signal) => {
|
|
760
|
+
const api = signal ? client.with({ signal }) : client;
|
|
679
761
|
if (!taskId) {
|
|
680
762
|
setComments([]);
|
|
681
763
|
setAttachments([]);
|
|
@@ -685,22 +767,31 @@ export function useTaskActivity(taskId) {
|
|
|
685
767
|
try {
|
|
686
768
|
setLoading(true);
|
|
687
769
|
const [fetchedComments, fetchedAttachments] = await Promise.all([
|
|
688
|
-
|
|
689
|
-
|
|
770
|
+
api.getComments(taskId),
|
|
771
|
+
api.getAttachments({ taskId })
|
|
690
772
|
]);
|
|
773
|
+
if (signal?.aborted)
|
|
774
|
+
return;
|
|
691
775
|
setComments(fetchedComments);
|
|
692
776
|
setAttachments(fetchedAttachments);
|
|
693
777
|
setError(null);
|
|
694
778
|
}
|
|
695
779
|
catch (err) {
|
|
780
|
+
if (signal?.aborted)
|
|
781
|
+
return;
|
|
696
782
|
setError(err instanceof Error ? err : new Error(String(err)));
|
|
697
783
|
}
|
|
698
784
|
finally {
|
|
699
|
-
|
|
785
|
+
if (!signal?.aborted)
|
|
786
|
+
setLoading(false);
|
|
700
787
|
}
|
|
701
788
|
}, [client, taskId]);
|
|
702
789
|
useEffect(() => {
|
|
703
|
-
|
|
790
|
+
// Abort the in-flight request when inputs change or the component unmounts, so a slow
|
|
791
|
+
// response for old inputs can never overwrite newer data.
|
|
792
|
+
const controller = new AbortController();
|
|
793
|
+
fetchActivity(controller.signal);
|
|
794
|
+
return () => controller.abort();
|
|
704
795
|
}, [fetchActivity]);
|
|
705
796
|
const threads = useMemo(() => {
|
|
706
797
|
const map = new Map();
|
|
@@ -841,7 +932,7 @@ export function useTaskActivity(taskId) {
|
|
|
841
932
|
standaloneAttachments,
|
|
842
933
|
loading,
|
|
843
934
|
error,
|
|
844
|
-
refresh: fetchActivity,
|
|
935
|
+
refresh: () => fetchActivity(),
|
|
845
936
|
addComment,
|
|
846
937
|
addAttachment,
|
|
847
938
|
deleteComment,
|
|
@@ -855,7 +946,8 @@ export function useCriticalPath(projectId) {
|
|
|
855
946
|
const [analysis, setAnalysis] = useState(null);
|
|
856
947
|
const [loading, setLoading] = useState(true);
|
|
857
948
|
const [error, setError] = useState(null);
|
|
858
|
-
const fetchCriticalPath = useCallback(async () => {
|
|
949
|
+
const fetchCriticalPath = useCallback(async (signal) => {
|
|
950
|
+
const api = signal ? client.with({ signal }) : client;
|
|
859
951
|
if (!projectId) {
|
|
860
952
|
setAnalysis(null);
|
|
861
953
|
setLoading(false);
|
|
@@ -863,25 +955,34 @@ export function useCriticalPath(projectId) {
|
|
|
863
955
|
}
|
|
864
956
|
try {
|
|
865
957
|
setLoading(true);
|
|
866
|
-
const data = await
|
|
958
|
+
const data = await api.calculateCriticalPath(projectId);
|
|
959
|
+
if (signal?.aborted)
|
|
960
|
+
return;
|
|
867
961
|
setAnalysis(data);
|
|
868
962
|
setError(null);
|
|
869
963
|
}
|
|
870
964
|
catch (err) {
|
|
965
|
+
if (signal?.aborted)
|
|
966
|
+
return;
|
|
871
967
|
setError(err instanceof Error ? err : new Error(String(err)));
|
|
872
968
|
}
|
|
873
969
|
finally {
|
|
874
|
-
|
|
970
|
+
if (!signal?.aborted)
|
|
971
|
+
setLoading(false);
|
|
875
972
|
}
|
|
876
973
|
}, [client, projectId]);
|
|
877
974
|
useEffect(() => {
|
|
878
|
-
|
|
975
|
+
// Abort the in-flight request when inputs change or the component unmounts, so a slow
|
|
976
|
+
// response for old inputs can never overwrite newer data.
|
|
977
|
+
const controller = new AbortController();
|
|
978
|
+
fetchCriticalPath(controller.signal);
|
|
979
|
+
return () => controller.abort();
|
|
879
980
|
}, [fetchCriticalPath]);
|
|
880
981
|
return {
|
|
881
982
|
analysis,
|
|
882
983
|
loading,
|
|
883
984
|
error,
|
|
884
|
-
refresh: fetchCriticalPath
|
|
985
|
+
refresh: () => fetchCriticalPath()
|
|
885
986
|
};
|
|
886
987
|
}
|
|
887
988
|
export function useTimelineLadder(projectId, initialOptions = {}) {
|
|
@@ -890,7 +991,8 @@ export function useTimelineLadder(projectId, initialOptions = {}) {
|
|
|
890
991
|
const [ladder, setLadder] = useState(null);
|
|
891
992
|
const [loading, setLoading] = useState(true);
|
|
892
993
|
const [error, setError] = useState(null);
|
|
893
|
-
const fetchLadder = useCallback(async () => {
|
|
994
|
+
const fetchLadder = useCallback(async (signal) => {
|
|
995
|
+
const api = signal ? client.with({ signal }) : client;
|
|
894
996
|
if (!projectId) {
|
|
895
997
|
setLadder(null);
|
|
896
998
|
setLoading(false);
|
|
@@ -898,22 +1000,31 @@ export function useTimelineLadder(projectId, initialOptions = {}) {
|
|
|
898
1000
|
}
|
|
899
1001
|
try {
|
|
900
1002
|
setLoading(true);
|
|
901
|
-
const data = await
|
|
1003
|
+
const data = await api.getTimelineLadder(projectId, {
|
|
902
1004
|
...initialOptions,
|
|
903
1005
|
level
|
|
904
1006
|
});
|
|
1007
|
+
if (signal?.aborted)
|
|
1008
|
+
return;
|
|
905
1009
|
setLadder(data);
|
|
906
1010
|
setError(null);
|
|
907
1011
|
}
|
|
908
1012
|
catch (err) {
|
|
1013
|
+
if (signal?.aborted)
|
|
1014
|
+
return;
|
|
909
1015
|
setError(err instanceof Error ? err : new Error(String(err)));
|
|
910
1016
|
}
|
|
911
1017
|
finally {
|
|
912
|
-
|
|
1018
|
+
if (!signal?.aborted)
|
|
1019
|
+
setLoading(false);
|
|
913
1020
|
}
|
|
914
1021
|
}, [client, projectId, level, initialOptions.containerId, initialOptions.iterationId]);
|
|
915
1022
|
useEffect(() => {
|
|
916
|
-
|
|
1023
|
+
// Abort the in-flight request when inputs change or the component unmounts, so a slow
|
|
1024
|
+
// response for old inputs can never overwrite newer data.
|
|
1025
|
+
const controller = new AbortController();
|
|
1026
|
+
fetchLadder(controller.signal);
|
|
1027
|
+
return () => controller.abort();
|
|
917
1028
|
}, [fetchLadder]);
|
|
918
1029
|
return {
|
|
919
1030
|
ladder,
|
|
@@ -924,7 +1035,7 @@ export function useTimelineLadder(projectId, initialOptions = {}) {
|
|
|
924
1035
|
concrete: ladder?.concrete ?? null,
|
|
925
1036
|
loading,
|
|
926
1037
|
error,
|
|
927
|
-
refresh: fetchLadder
|
|
1038
|
+
refresh: () => fetchLadder()
|
|
928
1039
|
};
|
|
929
1040
|
}
|
|
930
1041
|
export function useTaskLadder(taskId) {
|
|
@@ -932,7 +1043,8 @@ export function useTaskLadder(taskId) {
|
|
|
932
1043
|
const [taskLadder, setTaskLadder] = useState(null);
|
|
933
1044
|
const [loading, setLoading] = useState(true);
|
|
934
1045
|
const [error, setError] = useState(null);
|
|
935
|
-
const fetchTaskLadder = useCallback(async () => {
|
|
1046
|
+
const fetchTaskLadder = useCallback(async (signal) => {
|
|
1047
|
+
const api = signal ? client.with({ signal }) : client;
|
|
936
1048
|
if (!taskId) {
|
|
937
1049
|
setTaskLadder(null);
|
|
938
1050
|
setLoading(false);
|
|
@@ -940,19 +1052,28 @@ export function useTaskLadder(taskId) {
|
|
|
940
1052
|
}
|
|
941
1053
|
try {
|
|
942
1054
|
setLoading(true);
|
|
943
|
-
const data = await
|
|
1055
|
+
const data = await api.getTaskLadder(taskId);
|
|
1056
|
+
if (signal?.aborted)
|
|
1057
|
+
return;
|
|
944
1058
|
setTaskLadder(data);
|
|
945
1059
|
setError(null);
|
|
946
1060
|
}
|
|
947
1061
|
catch (err) {
|
|
1062
|
+
if (signal?.aborted)
|
|
1063
|
+
return;
|
|
948
1064
|
setError(err instanceof Error ? err : new Error(String(err)));
|
|
949
1065
|
}
|
|
950
1066
|
finally {
|
|
951
|
-
|
|
1067
|
+
if (!signal?.aborted)
|
|
1068
|
+
setLoading(false);
|
|
952
1069
|
}
|
|
953
1070
|
}, [client, taskId]);
|
|
954
1071
|
useEffect(() => {
|
|
955
|
-
|
|
1072
|
+
// Abort the in-flight request when inputs change or the component unmounts, so a slow
|
|
1073
|
+
// response for old inputs can never overwrite newer data.
|
|
1074
|
+
const controller = new AbortController();
|
|
1075
|
+
fetchTaskLadder(controller.signal);
|
|
1076
|
+
return () => controller.abort();
|
|
956
1077
|
}, [fetchTaskLadder]);
|
|
957
1078
|
return {
|
|
958
1079
|
taskLadder,
|
|
@@ -962,7 +1083,7 @@ export function useTaskLadder(taskId) {
|
|
|
962
1083
|
metrics: taskLadder?.metrics ?? null,
|
|
963
1084
|
loading,
|
|
964
1085
|
error,
|
|
965
|
-
refresh: fetchTaskLadder
|
|
1086
|
+
refresh: () => fetchTaskLadder()
|
|
966
1087
|
};
|
|
967
1088
|
}
|
|
968
1089
|
export function useTaskMetrics(taskId) {
|
|
@@ -971,7 +1092,8 @@ export function useTaskMetrics(taskId) {
|
|
|
971
1092
|
const [history, setHistory] = useState(null);
|
|
972
1093
|
const [loading, setLoading] = useState(true);
|
|
973
1094
|
const [error, setError] = useState(null);
|
|
974
|
-
const fetchMetrics = useCallback(async () => {
|
|
1095
|
+
const fetchMetrics = useCallback(async (signal) => {
|
|
1096
|
+
const api = signal ? client.with({ signal }) : client;
|
|
975
1097
|
if (!taskId) {
|
|
976
1098
|
setMetrics(null);
|
|
977
1099
|
setHistory(null);
|
|
@@ -981,22 +1103,31 @@ export function useTaskMetrics(taskId) {
|
|
|
981
1103
|
try {
|
|
982
1104
|
setLoading(true);
|
|
983
1105
|
const [m, h] = await Promise.all([
|
|
984
|
-
|
|
985
|
-
|
|
1106
|
+
api.getTaskMetrics(taskId),
|
|
1107
|
+
api.getTaskProgressHistory(taskId)
|
|
986
1108
|
]);
|
|
1109
|
+
if (signal?.aborted)
|
|
1110
|
+
return;
|
|
987
1111
|
setMetrics(m);
|
|
988
1112
|
setHistory(h);
|
|
989
1113
|
setError(null);
|
|
990
1114
|
}
|
|
991
1115
|
catch (err) {
|
|
1116
|
+
if (signal?.aborted)
|
|
1117
|
+
return;
|
|
992
1118
|
setError(err instanceof Error ? err : new Error(String(err)));
|
|
993
1119
|
}
|
|
994
1120
|
finally {
|
|
995
|
-
|
|
1121
|
+
if (!signal?.aborted)
|
|
1122
|
+
setLoading(false);
|
|
996
1123
|
}
|
|
997
1124
|
}, [client, taskId]);
|
|
998
1125
|
useEffect(() => {
|
|
999
|
-
|
|
1126
|
+
// Abort the in-flight request when inputs change or the component unmounts, so a slow
|
|
1127
|
+
// response for old inputs can never overwrite newer data.
|
|
1128
|
+
const controller = new AbortController();
|
|
1129
|
+
fetchMetrics(controller.signal);
|
|
1130
|
+
return () => controller.abort();
|
|
1000
1131
|
}, [fetchMetrics]);
|
|
1001
1132
|
return {
|
|
1002
1133
|
metrics,
|
|
@@ -1008,7 +1139,7 @@ export function useTaskMetrics(taskId) {
|
|
|
1008
1139
|
curveProfile: history?.curveProfile ?? null,
|
|
1009
1140
|
loading,
|
|
1010
1141
|
error,
|
|
1011
|
-
refresh: fetchMetrics
|
|
1142
|
+
refresh: () => fetchMetrics()
|
|
1012
1143
|
};
|
|
1013
1144
|
}
|
|
1014
1145
|
export function useWorkloadDistribution(projectId, initialOptions = {}) {
|
|
@@ -1019,23 +1150,29 @@ export function useWorkloadDistribution(projectId, initialOptions = {}) {
|
|
|
1019
1150
|
const [workload, setWorkload] = useState(null);
|
|
1020
1151
|
const [loading, setLoading] = useState(true);
|
|
1021
1152
|
const [error, setError] = useState(null);
|
|
1022
|
-
const fetchWorkload = useCallback(async () => {
|
|
1153
|
+
const fetchWorkload = useCallback(async (signal) => {
|
|
1154
|
+
const api = signal ? client.with({ signal }) : client;
|
|
1023
1155
|
try {
|
|
1024
1156
|
setLoading(true);
|
|
1025
|
-
const data = await
|
|
1157
|
+
const data = await api.getWorkloadDistribution(projectId, {
|
|
1026
1158
|
...initialOptions,
|
|
1027
1159
|
interval,
|
|
1028
1160
|
groupBy,
|
|
1029
1161
|
metric
|
|
1030
1162
|
});
|
|
1163
|
+
if (signal?.aborted)
|
|
1164
|
+
return;
|
|
1031
1165
|
setWorkload(data);
|
|
1032
1166
|
setError(null);
|
|
1033
1167
|
}
|
|
1034
1168
|
catch (err) {
|
|
1169
|
+
if (signal?.aborted)
|
|
1170
|
+
return;
|
|
1035
1171
|
setError(err instanceof Error ? err : new Error(String(err)));
|
|
1036
1172
|
}
|
|
1037
1173
|
finally {
|
|
1038
|
-
|
|
1174
|
+
if (!signal?.aborted)
|
|
1175
|
+
setLoading(false);
|
|
1039
1176
|
}
|
|
1040
1177
|
}, [
|
|
1041
1178
|
client,
|
|
@@ -1048,7 +1185,11 @@ export function useWorkloadDistribution(projectId, initialOptions = {}) {
|
|
|
1048
1185
|
initialOptions.defaultWeeklyCapacityHours
|
|
1049
1186
|
]);
|
|
1050
1187
|
useEffect(() => {
|
|
1051
|
-
|
|
1188
|
+
// Abort the in-flight request when inputs change or the component unmounts, so a slow
|
|
1189
|
+
// response for old inputs can never overwrite newer data.
|
|
1190
|
+
const controller = new AbortController();
|
|
1191
|
+
fetchWorkload(controller.signal);
|
|
1192
|
+
return () => controller.abort();
|
|
1052
1193
|
}, [fetchWorkload]);
|
|
1053
1194
|
return {
|
|
1054
1195
|
workload,
|
|
@@ -1066,7 +1207,7 @@ export function useWorkloadDistribution(projectId, initialOptions = {}) {
|
|
|
1066
1207
|
averageUtilization: workload?.averageUtilization,
|
|
1067
1208
|
loading,
|
|
1068
1209
|
error,
|
|
1069
|
-
refresh: fetchWorkload
|
|
1210
|
+
refresh: () => fetchWorkload()
|
|
1070
1211
|
};
|
|
1071
1212
|
}
|
|
1072
1213
|
//# sourceMappingURL=hooks.js.map
|