github-issue-tower-defence-management 2.12.1 → 2.13.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 +7 -0
- package/bin/adapter/entry-points/console/ui-dist/assets/{index-D6QOQvoN.js → index-BTFgj8n5.js} +31 -31
- package/bin/adapter/entry-points/console/ui-dist/index.html +1 -1
- package/package.json +1 -1
- package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleOfflinePendingActionsPanel.stories.tsx +115 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleOfflinePendingActionsPanel.test.tsx +262 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleOfflinePendingActionsPanel.tsx +123 -0
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleActionQueue.test.ts +283 -0
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleActionQueue.ts +146 -4
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOperations.ts +28 -1
- package/src/adapter/entry-points/console/ui/src/features/console/lib/consoleApi.ts +2 -2
- package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsoleItemDetailContainer.tsx +103 -1
- package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.tsx +128 -0
- package/src/adapter/entry-points/console/ui-dist/assets/{index-D6QOQvoN.js → index-BTFgj8n5.js} +31 -31
- package/src/adapter/entry-points/console/ui-dist/index.html +1 -1
|
@@ -3,16 +3,28 @@ import { ConsoleCommentComposer } from '../components/detail/ConsoleCommentCompo
|
|
|
3
3
|
import type { ConsoleAddInlineComment } from '../components/detail/ConsoleFileDiff';
|
|
4
4
|
import { ConsoleItemDetail } from '../components/detail/ConsoleItemDetail';
|
|
5
5
|
import { ConsoleOperationMenu } from '../components/operations/ConsoleOperationMenu';
|
|
6
|
+
import type { ConsoleOfflinePayload } from '../hooks/useConsoleActionQueue';
|
|
6
7
|
import type { ConsoleCaches } from '../hooks/useConsoleCaches';
|
|
7
8
|
import { useConsoleItemDetailData } from '../hooks/useConsoleItemDetailData';
|
|
8
|
-
import
|
|
9
|
+
import {
|
|
10
|
+
buildIntmuxRequest,
|
|
11
|
+
buildTriageRequest,
|
|
12
|
+
type ConsoleOperationsApi,
|
|
13
|
+
INTMUX_OPERATION_PATH,
|
|
14
|
+
REVIEW_OPERATION_PATH,
|
|
15
|
+
reviewRequest,
|
|
16
|
+
TRIAGE_OPERATION_PATH,
|
|
17
|
+
} from '../hooks/useConsoleOperations';
|
|
9
18
|
import { buildImageProxyUrl } from '../lib/imageProxy';
|
|
10
19
|
import type { ConsoleActionKind } from '../logic/actionToast';
|
|
11
20
|
import { resolveStoryColorEnum } from '../logic/grouping';
|
|
12
21
|
import {
|
|
13
22
|
AWAITING_WORKSPACE_NAME,
|
|
23
|
+
type ConsoleCloseAction,
|
|
24
|
+
type ConsoleNextActionDateAction,
|
|
14
25
|
type ConsoleOperationHandlers,
|
|
15
26
|
type ConsolePendingReviewComment,
|
|
27
|
+
type ConsoleReviewAction,
|
|
16
28
|
} from '../logic/operations';
|
|
17
29
|
import { mergePostedComments } from '../logic/postedComments';
|
|
18
30
|
import type {
|
|
@@ -30,13 +42,70 @@ export type ConsoleQueueActionInput = {
|
|
|
30
42
|
kind: ConsoleActionKind;
|
|
31
43
|
item: ConsoleListItem;
|
|
32
44
|
commit: () => Promise<void>;
|
|
45
|
+
offline?: ConsoleOfflinePayload;
|
|
33
46
|
};
|
|
34
47
|
|
|
48
|
+
const itemOfflineBase = (item: ConsoleListItem) => ({
|
|
49
|
+
itemUrl: item.url,
|
|
50
|
+
projectItemId: item.projectItemId,
|
|
51
|
+
itemNumber: item.number,
|
|
52
|
+
repo: item.repo,
|
|
53
|
+
isPr: item.isPr,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const buildReviewOfflinePayload = (
|
|
57
|
+
pjcode: string,
|
|
58
|
+
item: ConsoleListItem,
|
|
59
|
+
prUrl: string,
|
|
60
|
+
action: ConsoleReviewAction,
|
|
61
|
+
pendingComments: ConsolePendingReviewComment[],
|
|
62
|
+
): ConsoleOfflinePayload => ({
|
|
63
|
+
...itemOfflineBase(item),
|
|
64
|
+
apiPath: REVIEW_OPERATION_PATH,
|
|
65
|
+
requestBody: reviewRequest(
|
|
66
|
+
pjcode,
|
|
67
|
+
item,
|
|
68
|
+
prUrl,
|
|
69
|
+
action,
|
|
70
|
+
pendingComments,
|
|
71
|
+
) as unknown as Record<string, unknown>,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const buildTriageOfflinePayload = (
|
|
75
|
+
pjcode: string,
|
|
76
|
+
item: ConsoleListItem,
|
|
77
|
+
action:
|
|
78
|
+
| ConsoleNextActionDateAction
|
|
79
|
+
| ConsoleCloseAction
|
|
80
|
+
| 'set_story'
|
|
81
|
+
| 'set_status',
|
|
82
|
+
extra?: { statusName?: string; storyOptionId?: string },
|
|
83
|
+
): ConsoleOfflinePayload => ({
|
|
84
|
+
...itemOfflineBase(item),
|
|
85
|
+
apiPath: TRIAGE_OPERATION_PATH,
|
|
86
|
+
requestBody: buildTriageRequest(
|
|
87
|
+
pjcode,
|
|
88
|
+
item,
|
|
89
|
+
action,
|
|
90
|
+
extra,
|
|
91
|
+
) as unknown as Record<string, unknown>,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const buildIntmuxOfflinePayload = (
|
|
95
|
+
pjcode: string,
|
|
96
|
+
item: ConsoleListItem,
|
|
97
|
+
): ConsoleOfflinePayload => ({
|
|
98
|
+
...itemOfflineBase(item),
|
|
99
|
+
apiPath: INTMUX_OPERATION_PATH,
|
|
100
|
+
requestBody: buildIntmuxRequest(pjcode, item),
|
|
101
|
+
});
|
|
102
|
+
|
|
35
103
|
export type ConsoleItemDetailContainerProps = {
|
|
36
104
|
tab: ConsoleTabName;
|
|
37
105
|
item: ConsoleListItem;
|
|
38
106
|
caches: ConsoleCaches;
|
|
39
107
|
operations: ConsoleOperationsApi;
|
|
108
|
+
pjcode?: string | null;
|
|
40
109
|
statusOptions: ConsoleFieldOption[];
|
|
41
110
|
storyColors: ConsoleStoryColorSource;
|
|
42
111
|
storyName: string | null;
|
|
@@ -52,6 +121,7 @@ export const ConsoleItemDetailContainer = ({
|
|
|
52
121
|
item,
|
|
53
122
|
caches,
|
|
54
123
|
operations,
|
|
124
|
+
pjcode,
|
|
55
125
|
statusOptions,
|
|
56
126
|
storyColors,
|
|
57
127
|
storyName,
|
|
@@ -116,6 +186,16 @@ export const ConsoleItemDetailContainer = ({
|
|
|
116
186
|
item,
|
|
117
187
|
commit: () =>
|
|
118
188
|
operations.reviewPullRequest(item, prUrl, action, reviewComments),
|
|
189
|
+
offline:
|
|
190
|
+
pjcode != null
|
|
191
|
+
? buildReviewOfflinePayload(
|
|
192
|
+
pjcode,
|
|
193
|
+
item,
|
|
194
|
+
prUrl,
|
|
195
|
+
action,
|
|
196
|
+
reviewComments,
|
|
197
|
+
)
|
|
198
|
+
: undefined,
|
|
119
199
|
});
|
|
120
200
|
},
|
|
121
201
|
onSetNextActionDate: (action) => {
|
|
@@ -123,6 +203,10 @@ export const ConsoleItemDetailContainer = ({
|
|
|
123
203
|
kind: { type: 'next_action_date', action },
|
|
124
204
|
item,
|
|
125
205
|
commit: () => operations.setNextActionDate(item, action),
|
|
206
|
+
offline:
|
|
207
|
+
pjcode != null
|
|
208
|
+
? buildTriageOfflinePayload(pjcode, item, action)
|
|
209
|
+
: undefined,
|
|
126
210
|
});
|
|
127
211
|
},
|
|
128
212
|
onSetStory: (option: ConsoleFieldOption) => {
|
|
@@ -130,6 +214,12 @@ export const ConsoleItemDetailContainer = ({
|
|
|
130
214
|
kind: { type: 'set_story', optionName: option.name },
|
|
131
215
|
item,
|
|
132
216
|
commit: () => operations.setStory(item, option),
|
|
217
|
+
offline:
|
|
218
|
+
pjcode != null
|
|
219
|
+
? buildTriageOfflinePayload(pjcode, item, 'set_story', {
|
|
220
|
+
storyOptionId: option.id,
|
|
221
|
+
})
|
|
222
|
+
: undefined,
|
|
133
223
|
});
|
|
134
224
|
},
|
|
135
225
|
onSetStatus: (option: ConsoleFieldOption) => {
|
|
@@ -137,6 +227,12 @@ export const ConsoleItemDetailContainer = ({
|
|
|
137
227
|
kind: { type: 'set_status', optionName: option.name },
|
|
138
228
|
item,
|
|
139
229
|
commit: () => operations.setStatus(item, option),
|
|
230
|
+
offline:
|
|
231
|
+
pjcode != null
|
|
232
|
+
? buildTriageOfflinePayload(pjcode, item, 'set_status', {
|
|
233
|
+
statusName: option.name,
|
|
234
|
+
})
|
|
235
|
+
: undefined,
|
|
140
236
|
});
|
|
141
237
|
},
|
|
142
238
|
onSetInTmuxByHuman: (option: ConsoleFieldOption) => {
|
|
@@ -144,6 +240,8 @@ export const ConsoleItemDetailContainer = ({
|
|
|
144
240
|
kind: { type: 'set_in_tmux_by_human', optionName: option.name },
|
|
145
241
|
item,
|
|
146
242
|
commit: () => operations.setInTmuxByHuman(item, option),
|
|
243
|
+
offline:
|
|
244
|
+
pjcode != null ? buildIntmuxOfflinePayload(pjcode, item) : undefined,
|
|
147
245
|
});
|
|
148
246
|
},
|
|
149
247
|
onClose: (action) => {
|
|
@@ -151,6 +249,10 @@ export const ConsoleItemDetailContainer = ({
|
|
|
151
249
|
kind: { type: 'close', action },
|
|
152
250
|
item,
|
|
153
251
|
commit: () => operations.closeIssue(item, action),
|
|
252
|
+
offline:
|
|
253
|
+
pjcode != null
|
|
254
|
+
? buildTriageOfflinePayload(pjcode, item, action)
|
|
255
|
+
: undefined,
|
|
154
256
|
});
|
|
155
257
|
},
|
|
156
258
|
onOkAndAwaitingWorkspace: (option: ConsoleFieldOption) => {
|
|
@@ -4,6 +4,10 @@ import { ConsoleTimerSettingsModalDialog } from '../components/layout/ConsoleTim
|
|
|
4
4
|
import { ConsoleItemList } from '../components/list/ConsoleItemList';
|
|
5
5
|
import { ConsoleQueuedList } from '../components/list/ConsoleQueuedList';
|
|
6
6
|
import { ConsoleStoryList } from '../components/list/ConsoleStoryList';
|
|
7
|
+
import {
|
|
8
|
+
type ConsoleOfflinePendingActionItem,
|
|
9
|
+
ConsoleOfflinePendingActionsPanel,
|
|
10
|
+
} from '../components/operations/ConsoleOfflinePendingActionsPanel';
|
|
7
11
|
import {
|
|
8
12
|
ConsoleErrorToast,
|
|
9
13
|
ConsoleUndoToast,
|
|
@@ -52,8 +56,10 @@ import { findNextNonEmptyTabToRight } from '../logic/tabAdvance';
|
|
|
52
56
|
import { findNextPjcodeWithMinutes } from '../logic/timerSettings';
|
|
53
57
|
import type {
|
|
54
58
|
ConsoleColor,
|
|
59
|
+
ConsoleIssueState,
|
|
55
60
|
ConsoleListItem,
|
|
56
61
|
ConsoleOverlayStatus,
|
|
62
|
+
ConsolePullRequestStatus,
|
|
57
63
|
ConsoleStoryEntry,
|
|
58
64
|
ConsoleTabName,
|
|
59
65
|
} from '../logic/types';
|
|
@@ -151,6 +157,119 @@ export const ConsolePage = () => {
|
|
|
151
157
|
const actionQueue = useConsoleActionQueue();
|
|
152
158
|
const now = Date.now();
|
|
153
159
|
|
|
160
|
+
const [isOnline, setIsOnline] = useState(() => navigator.onLine);
|
|
161
|
+
useEffect(() => {
|
|
162
|
+
const handleOnline = (): void => setIsOnline(true);
|
|
163
|
+
const handleOffline = (): void => setIsOnline(false);
|
|
164
|
+
window.addEventListener('online', handleOnline);
|
|
165
|
+
window.addEventListener('offline', handleOffline);
|
|
166
|
+
return () => {
|
|
167
|
+
window.removeEventListener('online', handleOnline);
|
|
168
|
+
window.removeEventListener('offline', handleOffline);
|
|
169
|
+
};
|
|
170
|
+
}, []);
|
|
171
|
+
|
|
172
|
+
const [offlineItemStates, setOfflineItemStates] = useState<
|
|
173
|
+
Map<string, ConsoleIssueState | null>
|
|
174
|
+
>(new Map());
|
|
175
|
+
|
|
176
|
+
const [offlinePrStatuses, setOfflinePrStatuses] = useState<
|
|
177
|
+
Map<string, ConsolePullRequestStatus | null>
|
|
178
|
+
>(new Map());
|
|
179
|
+
|
|
180
|
+
const [offlineFetchErrors, setOfflineFetchErrors] = useState<Set<string>>(
|
|
181
|
+
new Set(),
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
useEffect(() => {
|
|
185
|
+
if (!isOnline || actionQueue.offlineActions.length === 0) return;
|
|
186
|
+
for (const action of actionQueue.offlineActions) {
|
|
187
|
+
if (offlineItemStates.has(action.id) || offlineFetchErrors.has(action.id))
|
|
188
|
+
continue;
|
|
189
|
+
setOfflineItemStates((prev) => new Map(prev).set(action.id, null));
|
|
190
|
+
caches.client
|
|
191
|
+
.fetchIssueState(action.itemUrl)
|
|
192
|
+
.then((state) => {
|
|
193
|
+
setOfflineItemStates((prev) => new Map(prev).set(action.id, state));
|
|
194
|
+
if (action.isPr) {
|
|
195
|
+
setOfflinePrStatuses((prev) => new Map(prev).set(action.id, null));
|
|
196
|
+
caches.client
|
|
197
|
+
.fetchPullRequestStatus(action.itemUrl)
|
|
198
|
+
.then((prStatus) => {
|
|
199
|
+
setOfflinePrStatuses((prev) =>
|
|
200
|
+
new Map(prev).set(action.id, prStatus),
|
|
201
|
+
);
|
|
202
|
+
})
|
|
203
|
+
.catch((fetchError: unknown) => {
|
|
204
|
+
console.warn(
|
|
205
|
+
'Failed to fetch PR status for offline action:',
|
|
206
|
+
fetchError,
|
|
207
|
+
);
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
})
|
|
211
|
+
.catch((fetchError: unknown) => {
|
|
212
|
+
console.warn(
|
|
213
|
+
'Failed to fetch current state for offline action:',
|
|
214
|
+
fetchError,
|
|
215
|
+
);
|
|
216
|
+
setOfflineFetchErrors((prev) => new Set(prev).add(action.id));
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
}, [
|
|
220
|
+
isOnline,
|
|
221
|
+
actionQueue.offlineActions,
|
|
222
|
+
caches.client,
|
|
223
|
+
offlineItemStates,
|
|
224
|
+
offlineFetchErrors,
|
|
225
|
+
]);
|
|
226
|
+
|
|
227
|
+
const [isConfirmingOffline, setIsConfirmingOffline] = useState(false);
|
|
228
|
+
|
|
229
|
+
const handleConfirmOffline = useCallback(
|
|
230
|
+
async (id: string): Promise<void> => {
|
|
231
|
+
setIsConfirmingOffline(true);
|
|
232
|
+
try {
|
|
233
|
+
await actionQueue.confirmOfflineAction(id);
|
|
234
|
+
} finally {
|
|
235
|
+
setIsConfirmingOffline(false);
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
[actionQueue],
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
const offlinePanelActions = useMemo(
|
|
242
|
+
(): ConsoleOfflinePendingActionItem[] =>
|
|
243
|
+
actionQueue.offlineActions.map((a) => {
|
|
244
|
+
const state = offlineItemStates.get(a.id) ?? null;
|
|
245
|
+
const prStatus = offlinePrStatuses.get(a.id) ?? null;
|
|
246
|
+
const fetchError = offlineFetchErrors.has(a.id);
|
|
247
|
+
return {
|
|
248
|
+
id: a.id,
|
|
249
|
+
message: a.message,
|
|
250
|
+
color: a.color,
|
|
251
|
+
itemNumber: a.itemNumber,
|
|
252
|
+
isPr: a.isPr,
|
|
253
|
+
currentTitle: fetchError ? null : (state?.title ?? null),
|
|
254
|
+
currentState: fetchError
|
|
255
|
+
? null
|
|
256
|
+
: state === null
|
|
257
|
+
? null
|
|
258
|
+
: state.merged
|
|
259
|
+
? 'closed'
|
|
260
|
+
: (state.state as 'open' | 'closed'),
|
|
261
|
+
currentPrStatus: prStatus,
|
|
262
|
+
fetchError,
|
|
263
|
+
};
|
|
264
|
+
}),
|
|
265
|
+
[
|
|
266
|
+
actionQueue.offlineActions,
|
|
267
|
+
offlineItemStates,
|
|
268
|
+
offlinePrStatuses,
|
|
269
|
+
offlineFetchErrors,
|
|
270
|
+
],
|
|
271
|
+
);
|
|
272
|
+
|
|
154
273
|
const activeSnapshot = snapshots[activeTab];
|
|
155
274
|
const pendingItems = useMemo(() => {
|
|
156
275
|
if (activeSnapshot === null) {
|
|
@@ -294,6 +413,7 @@ export const ConsolePage = () => {
|
|
|
294
413
|
message: formatActionToast(input.kind, input.item, activeTab),
|
|
295
414
|
color: actionToastColor(input.kind),
|
|
296
415
|
commit: input.commit,
|
|
416
|
+
offline: input.offline,
|
|
297
417
|
advance: () => {
|
|
298
418
|
if (actionAdvances(input.kind, activeTab)) {
|
|
299
419
|
if (
|
|
@@ -506,6 +626,13 @@ export const ConsolePage = () => {
|
|
|
506
626
|
onDismiss={actionQueue.dismissError}
|
|
507
627
|
/>
|
|
508
628
|
)}
|
|
629
|
+
<ConsoleOfflinePendingActionsPanel
|
|
630
|
+
actions={offlinePanelActions}
|
|
631
|
+
isOnline={isOnline}
|
|
632
|
+
isConfirming={isConfirmingOffline}
|
|
633
|
+
onConfirm={handleConfirmOffline}
|
|
634
|
+
onDiscard={actionQueue.discardOfflineAction}
|
|
635
|
+
/>
|
|
509
636
|
<ConsoleTabList
|
|
510
637
|
activeTab={activeTab}
|
|
511
638
|
counts={counts}
|
|
@@ -585,6 +712,7 @@ export const ConsolePage = () => {
|
|
|
585
712
|
item={selectedItem}
|
|
586
713
|
caches={caches}
|
|
587
714
|
operations={operations}
|
|
715
|
+
pjcode={pjcode}
|
|
588
716
|
statusOptions={statusOptions}
|
|
589
717
|
storyColors={storyColors}
|
|
590
718
|
storyName={storyNameForSelected}
|