github-issue-tower-defence-management 1.89.0 → 1.91.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/.github/CODEOWNERS +1 -2
- package/CHANGELOG.md +14 -0
- package/README.md +15 -2
- package/bin/adapter/entry-points/cli/index.js +16 -12
- package/bin/adapter/entry-points/cli/index.js.map +1 -1
- package/bin/adapter/entry-points/cli/projectConfig.js +2 -0
- package/bin/adapter/entry-points/cli/projectConfig.js.map +1 -1
- package/bin/adapter/entry-points/console/consoleOperationApi.js +54 -27
- package/bin/adapter/entry-points/console/consoleOperationApi.js.map +1 -1
- package/bin/adapter/entry-points/console/consoleProjectResolver.js +38 -0
- package/bin/adapter/entry-points/console/consoleProjectResolver.js.map +1 -0
- package/bin/adapter/entry-points/console/consoleServer.js +43 -17
- package/bin/adapter/entry-points/console/consoleServer.js.map +1 -1
- package/bin/adapter/entry-points/console/ui-dist/assets/index-BU6p3cGU.css +1 -0
- package/bin/adapter/entry-points/console/ui-dist/assets/index-BvuSQN9s.js +100 -0
- package/bin/adapter/entry-points/console/ui-dist/index.html +2 -2
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js +16 -0
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js.map +1 -1
- package/jest.config.js +57 -9
- package/package.json +17 -13
- package/src/adapter/entry-points/cli/index.test.ts +12 -2
- package/src/adapter/entry-points/cli/index.ts +30 -12
- package/src/adapter/entry-points/cli/projectConfig.ts +3 -0
- package/src/adapter/entry-points/console/consoleOperationApi.test.ts +129 -15
- package/src/adapter/entry-points/console/consoleOperationApi.ts +83 -28
- package/src/adapter/entry-points/console/consoleProjectResolver.test.ts +96 -0
- package/src/adapter/entry-points/console/consoleProjectResolver.ts +50 -0
- package/src/adapter/entry-points/console/consoleServer.test.ts +86 -4
- package/src/adapter/entry-points/console/consoleServer.ts +53 -23
- package/src/adapter/entry-points/console/ui/jest.setup.ts +1 -0
- package/src/adapter/entry-points/console/ui/jest.styleMock.js +1 -0
- package/src/adapter/entry-points/console/ui/src/features/console/colors.test.ts +34 -0
- package/src/adapter/entry-points/console/ui/src/features/console/colors.ts +73 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleChangedFileList.stories.tsx +28 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleChangedFileList.test.tsx +42 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleChangedFileList.tsx +55 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleCloseButtonGroup.stories.tsx +14 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleCloseButtonGroup.test.tsx +23 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleCloseButtonGroup.tsx +26 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleCommentList.stories.tsx +29 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleCommentList.test.tsx +55 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleCommentList.tsx +66 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleCommitList.stories.tsx +25 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleCommitList.test.tsx +53 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleCommitList.tsx +53 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleItemDetail.stories.tsx +79 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleItemDetail.test.tsx +81 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleItemDetail.tsx +226 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleItemIcon.stories.tsx +82 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleItemIcon.test.tsx +52 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleItemIcon.tsx +32 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleListItemRow.stories.tsx +25 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleListItemRow.test.tsx +43 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleListItemRow.tsx +34 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleListView.stories.tsx +22 -6
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleListView.test.tsx +87 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleListView.tsx +36 -32
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleMarkdownView.stories.tsx +27 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleMarkdownView.test.tsx +36 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleMarkdownView.tsx +50 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleMermaidDiagram.stories.tsx +22 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleMermaidDiagram.test.tsx +38 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleMermaidDiagram.tsx +65 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleNextActionDateGroup.stories.tsx +20 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleNextActionDateGroup.test.tsx +42 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleNextActionDateGroup.tsx +28 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleOperationBar.stories.tsx +55 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleOperationBar.test.tsx +85 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleOperationBar.tsx +55 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsolePanel.stories.tsx +26 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsolePanel.test.tsx +32 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsolePanel.tsx +36 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleProjectHeader.stories.tsx +29 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleProjectHeader.test.tsx +14 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleProjectHeader.tsx +14 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsolePullRequestReviewGroup.stories.tsx +14 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsolePullRequestReviewGroup.test.tsx +33 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsolePullRequestReviewGroup.tsx +34 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsolePullRequestSection.stories.tsx +31 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsolePullRequestSection.test.tsx +40 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsolePullRequestSection.tsx +88 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleStatusButtonGroup.stories.tsx +17 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleStatusButtonGroup.test.tsx +49 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleStatusButtonGroup.tsx +63 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleStoryButtonGroup.stories.tsx +17 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleStoryButtonGroup.test.tsx +45 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleStoryButtonGroup.tsx +42 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleStoryGroupHeader.stories.tsx +27 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleStoryGroupHeader.test.tsx +24 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleStoryGroupHeader.tsx +28 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleTabBar.stories.tsx +41 -5
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleTabBar.test.tsx +59 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleTabBar.tsx +28 -19
- package/src/adapter/entry-points/console/ui/src/features/console/fileStatus.test.ts +35 -0
- package/src/adapter/entry-points/console/ui/src/features/console/fileStatus.ts +21 -0
- package/src/adapter/entry-points/console/ui/src/features/console/fixtures.ts +206 -9
- package/src/adapter/entry-points/console/ui/src/features/console/grouping.test.ts +91 -0
- package/src/adapter/entry-points/console/ui/src/features/console/grouping.ts +79 -0
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleCaches.test.ts +22 -0
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleCaches.ts +42 -0
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleItemDetailData.test.ts +126 -0
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleItemDetailData.ts +167 -0
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOperations.test.ts +198 -0
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOperations.ts +243 -0
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOverlay.test.ts +40 -0
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOverlay.ts +71 -0
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsolePjcode.test.ts +24 -0
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsolePjcode.ts +17 -0
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleResource.test.ts +41 -0
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleResource.ts +57 -0
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTabData.test.ts +63 -0
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTabData.ts +129 -0
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleToken.test.ts +41 -0
- package/src/adapter/entry-points/console/ui/src/features/console/itemIcons.test.ts +97 -0
- package/src/adapter/entry-points/console/ui/src/features/console/itemIcons.ts +95 -0
- package/src/adapter/entry-points/console/ui/src/features/console/lib/consoleApi.test.ts +155 -0
- package/src/adapter/entry-points/console/ui/src/features/console/lib/consoleApi.ts +187 -0
- package/src/adapter/entry-points/console/ui/src/features/console/lib/markdown.test.ts +76 -0
- package/src/adapter/entry-points/console/ui/src/features/console/lib/markdown.ts +73 -0
- package/src/adapter/entry-points/console/ui/src/features/console/lib/mermaidLoader.test.ts +27 -0
- package/src/adapter/entry-points/console/ui/src/features/console/lib/mermaidLoader.ts +71 -0
- package/src/adapter/entry-points/console/ui/src/features/console/lib/resourceCache.test.ts +56 -0
- package/src/adapter/entry-points/console/ui/src/features/console/lib/resourceCache.ts +51 -0
- package/src/adapter/entry-points/console/ui/src/features/console/operations.test.ts +37 -0
- package/src/adapter/entry-points/console/ui/src/features/console/operations.ts +35 -0
- package/src/adapter/entry-points/console/ui/src/features/console/overlay.test.ts +124 -0
- package/src/adapter/entry-points/console/ui/src/features/console/overlay.ts +101 -0
- package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsoleItemDetailContainer.test.tsx +79 -0
- package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsoleItemDetailContainer.tsx +109 -0
- package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.test.tsx +74 -0
- package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.tsx +137 -7
- package/src/adapter/entry-points/console/ui/src/features/console/relativeTime.test.ts +52 -0
- package/src/adapter/entry-points/console/ui/src/features/console/relativeTime.ts +51 -0
- package/src/adapter/entry-points/console/ui/src/features/console/types.ts +73 -1
- package/src/adapter/entry-points/console/ui/src/index.css +352 -2
- package/src/adapter/entry-points/console/ui/tsconfig.json +3 -1
- package/src/adapter/entry-points/console/ui/vite.config.ts +6 -1
- package/src/adapter/entry-points/console/ui-dist/assets/index-BU6p3cGU.css +1 -0
- package/src/adapter/entry-points/console/ui-dist/assets/index-BvuSQN9s.js +100 -0
- package/src/adapter/entry-points/console/ui-dist/index.html +2 -2
- package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts +25 -0
- package/src/domain/usecases/adapter-interfaces/IssueRepository.ts +4 -0
- package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
- package/types/adapter/entry-points/cli/projectConfig.d.ts +1 -0
- package/types/adapter/entry-points/cli/projectConfig.d.ts.map +1 -1
- package/types/adapter/entry-points/console/consoleOperationApi.d.ts +6 -2
- package/types/adapter/entry-points/console/consoleOperationApi.d.ts.map +1 -1
- package/types/adapter/entry-points/console/consoleProjectResolver.d.ts +6 -0
- package/types/adapter/entry-points/console/consoleProjectResolver.d.ts.map +1 -0
- package/types/adapter/entry-points/console/consoleServer.d.ts +3 -3
- package/types/adapter/entry-points/console/consoleServer.d.ts.map +1 -1
- package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts +1 -0
- package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts.map +1 -1
- package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts +1 -0
- package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts.map +1 -1
- package/bin/adapter/entry-points/console/ui-dist/assets/index-DFxrSRH4.css +0 -1
- package/bin/adapter/entry-points/console/ui-dist/assets/index-DcOZ02ON.js +0 -49
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleList.ts +0 -65
- package/src/adapter/entry-points/console/ui-dist/assets/index-DFxrSRH4.css +0 -1
- package/src/adapter/entry-points/console/ui-dist/assets/index-DcOZ02ON.js +0 -49
package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOperations.ts
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
type ConsoleIntmuxRequest,
|
|
4
|
+
type ConsoleReviewRequest,
|
|
5
|
+
type ConsoleTriageRequest,
|
|
6
|
+
postConsoleOperation,
|
|
7
|
+
} from '../lib/consoleApi';
|
|
8
|
+
import {
|
|
9
|
+
type ConsoleCloseAction,
|
|
10
|
+
type ConsoleNextActionDateAction,
|
|
11
|
+
type ConsoleReviewAction,
|
|
12
|
+
TOTALLY_WRONG_COMMENT_BODY,
|
|
13
|
+
UNNECESSARY_COMMENT_BODY,
|
|
14
|
+
} from '../operations';
|
|
15
|
+
import { overlayKeyForItem } from '../overlay';
|
|
16
|
+
import type {
|
|
17
|
+
ConsoleFieldOption,
|
|
18
|
+
ConsoleListItem,
|
|
19
|
+
ConsoleTabName,
|
|
20
|
+
} from '../types';
|
|
21
|
+
import type { ConsoleOverlayState } from './useConsoleOverlay';
|
|
22
|
+
import { useConsoleToken } from './useConsoleToken';
|
|
23
|
+
|
|
24
|
+
export const REVIEW_OPERATION_PATH = '/api/review';
|
|
25
|
+
export const TRIAGE_OPERATION_PATH = '/api/triage';
|
|
26
|
+
export const INTMUX_OPERATION_PATH = '/api/intmux';
|
|
27
|
+
|
|
28
|
+
export type ConsoleOperationsApi = {
|
|
29
|
+
reviewPullRequest: (
|
|
30
|
+
item: ConsoleListItem,
|
|
31
|
+
prUrl: string,
|
|
32
|
+
action: ConsoleReviewAction,
|
|
33
|
+
) => Promise<void>;
|
|
34
|
+
setNextActionDate: (
|
|
35
|
+
item: ConsoleListItem,
|
|
36
|
+
action: ConsoleNextActionDateAction,
|
|
37
|
+
) => Promise<void>;
|
|
38
|
+
setStory: (
|
|
39
|
+
item: ConsoleListItem,
|
|
40
|
+
option: ConsoleFieldOption,
|
|
41
|
+
) => Promise<void>;
|
|
42
|
+
setStatus: (
|
|
43
|
+
item: ConsoleListItem,
|
|
44
|
+
option: ConsoleFieldOption,
|
|
45
|
+
) => Promise<void>;
|
|
46
|
+
setInTmuxByHuman: (
|
|
47
|
+
item: ConsoleListItem,
|
|
48
|
+
option: ConsoleFieldOption,
|
|
49
|
+
) => Promise<void>;
|
|
50
|
+
closeIssue: (
|
|
51
|
+
item: ConsoleListItem,
|
|
52
|
+
action: ConsoleCloseAction,
|
|
53
|
+
) => Promise<void>;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const reviewRequest = (
|
|
57
|
+
pjcode: string,
|
|
58
|
+
item: ConsoleListItem,
|
|
59
|
+
prUrl: string,
|
|
60
|
+
action: ConsoleReviewAction,
|
|
61
|
+
): ConsoleReviewRequest => {
|
|
62
|
+
if (action === 'approve') {
|
|
63
|
+
return {
|
|
64
|
+
pjcode,
|
|
65
|
+
action: 'approve',
|
|
66
|
+
prUrl,
|
|
67
|
+
projectItemId: item.projectItemId,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
if (action === 'request_changes') {
|
|
71
|
+
return {
|
|
72
|
+
pjcode,
|
|
73
|
+
action: 'request_changes',
|
|
74
|
+
prUrl,
|
|
75
|
+
projectItemId: item.projectItemId,
|
|
76
|
+
commentBody: '',
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
if (action === 'totally_wrong') {
|
|
80
|
+
return {
|
|
81
|
+
pjcode,
|
|
82
|
+
action: 'close',
|
|
83
|
+
prUrl,
|
|
84
|
+
projectItemId: item.projectItemId,
|
|
85
|
+
commentBody: TOTALLY_WRONG_COMMENT_BODY,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
pjcode,
|
|
90
|
+
action: 'close',
|
|
91
|
+
prUrl,
|
|
92
|
+
projectItemId: item.projectItemId,
|
|
93
|
+
commentBody: UNNECESSARY_COMMENT_BODY,
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const missingPjcodeError = (): Error =>
|
|
98
|
+
new Error('No project specified in the URL path.');
|
|
99
|
+
|
|
100
|
+
export const useConsoleOperations = (
|
|
101
|
+
pjcode: string | null,
|
|
102
|
+
mode: ConsoleTabName,
|
|
103
|
+
overlayState: ConsoleOverlayState,
|
|
104
|
+
): ConsoleOperationsApi => {
|
|
105
|
+
const { appendToken } = useConsoleToken();
|
|
106
|
+
const { patchOverlay } = overlayState;
|
|
107
|
+
|
|
108
|
+
const markDone = useCallback(
|
|
109
|
+
(item: ConsoleListItem) => {
|
|
110
|
+
patchOverlay(overlayKeyForItem(item), { done: true }, mode);
|
|
111
|
+
},
|
|
112
|
+
[patchOverlay, mode],
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const reviewPullRequest = useCallback(
|
|
116
|
+
async (
|
|
117
|
+
item: ConsoleListItem,
|
|
118
|
+
prUrl: string,
|
|
119
|
+
action: ConsoleReviewAction,
|
|
120
|
+
) => {
|
|
121
|
+
if (pjcode === null) {
|
|
122
|
+
throw missingPjcodeError();
|
|
123
|
+
}
|
|
124
|
+
await postConsoleOperation(
|
|
125
|
+
appendToken,
|
|
126
|
+
REVIEW_OPERATION_PATH,
|
|
127
|
+
reviewRequest(pjcode, item, prUrl, action),
|
|
128
|
+
);
|
|
129
|
+
markDone(item);
|
|
130
|
+
},
|
|
131
|
+
[pjcode, appendToken, markDone],
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
const setNextActionDate = useCallback(
|
|
135
|
+
async (item: ConsoleListItem, action: ConsoleNextActionDateAction) => {
|
|
136
|
+
if (pjcode === null) {
|
|
137
|
+
throw missingPjcodeError();
|
|
138
|
+
}
|
|
139
|
+
const request: ConsoleTriageRequest = {
|
|
140
|
+
pjcode,
|
|
141
|
+
action,
|
|
142
|
+
issueUrl: item.url,
|
|
143
|
+
projectItemId: item.projectItemId,
|
|
144
|
+
};
|
|
145
|
+
await postConsoleOperation(appendToken, TRIAGE_OPERATION_PATH, request);
|
|
146
|
+
if (mode === 'todo-by-human') {
|
|
147
|
+
markDone(item);
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
[pjcode, appendToken, markDone, mode],
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
const setStory = useCallback(
|
|
154
|
+
async (item: ConsoleListItem, option: ConsoleFieldOption) => {
|
|
155
|
+
if (pjcode === null) {
|
|
156
|
+
throw missingPjcodeError();
|
|
157
|
+
}
|
|
158
|
+
const request: ConsoleTriageRequest = {
|
|
159
|
+
pjcode,
|
|
160
|
+
action: 'set_story',
|
|
161
|
+
issueUrl: item.url,
|
|
162
|
+
projectItemId: item.projectItemId,
|
|
163
|
+
storyOptionId: option.id,
|
|
164
|
+
};
|
|
165
|
+
await postConsoleOperation(appendToken, TRIAGE_OPERATION_PATH, request);
|
|
166
|
+
patchOverlay(
|
|
167
|
+
overlayKeyForItem(item),
|
|
168
|
+
{ done: true, story: { name: option.name, color: option.color } },
|
|
169
|
+
mode,
|
|
170
|
+
);
|
|
171
|
+
},
|
|
172
|
+
[pjcode, appendToken, patchOverlay, mode],
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
const setStatus = useCallback(
|
|
176
|
+
async (item: ConsoleListItem, option: ConsoleFieldOption) => {
|
|
177
|
+
if (pjcode === null) {
|
|
178
|
+
throw missingPjcodeError();
|
|
179
|
+
}
|
|
180
|
+
const request: ConsoleTriageRequest = {
|
|
181
|
+
pjcode,
|
|
182
|
+
action: 'set_status',
|
|
183
|
+
issueUrl: item.url,
|
|
184
|
+
projectItemId: item.projectItemId,
|
|
185
|
+
statusName: option.name,
|
|
186
|
+
};
|
|
187
|
+
await postConsoleOperation(appendToken, TRIAGE_OPERATION_PATH, request);
|
|
188
|
+
patchOverlay(
|
|
189
|
+
overlayKeyForItem(item),
|
|
190
|
+
{ done: true, status: { name: option.name, color: option.color } },
|
|
191
|
+
mode,
|
|
192
|
+
);
|
|
193
|
+
},
|
|
194
|
+
[pjcode, appendToken, patchOverlay, mode],
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
const setInTmuxByHuman = useCallback(
|
|
198
|
+
async (item: ConsoleListItem, option: ConsoleFieldOption) => {
|
|
199
|
+
if (pjcode === null) {
|
|
200
|
+
throw missingPjcodeError();
|
|
201
|
+
}
|
|
202
|
+
const request: ConsoleIntmuxRequest = {
|
|
203
|
+
pjcode,
|
|
204
|
+
action: 'set_intmux',
|
|
205
|
+
issueUrl: item.url,
|
|
206
|
+
projectItemId: item.projectItemId,
|
|
207
|
+
};
|
|
208
|
+
await postConsoleOperation(appendToken, INTMUX_OPERATION_PATH, request);
|
|
209
|
+
patchOverlay(
|
|
210
|
+
overlayKeyForItem(item),
|
|
211
|
+
{ done: true, status: { name: option.name, color: option.color } },
|
|
212
|
+
mode,
|
|
213
|
+
);
|
|
214
|
+
},
|
|
215
|
+
[pjcode, appendToken, patchOverlay, mode],
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
const closeIssue = useCallback(
|
|
219
|
+
async (item: ConsoleListItem, action: ConsoleCloseAction) => {
|
|
220
|
+
if (pjcode === null) {
|
|
221
|
+
throw missingPjcodeError();
|
|
222
|
+
}
|
|
223
|
+
const request: ConsoleTriageRequest = {
|
|
224
|
+
pjcode,
|
|
225
|
+
action,
|
|
226
|
+
issueUrl: item.url,
|
|
227
|
+
projectItemId: item.projectItemId,
|
|
228
|
+
};
|
|
229
|
+
await postConsoleOperation(appendToken, TRIAGE_OPERATION_PATH, request);
|
|
230
|
+
markDone(item);
|
|
231
|
+
},
|
|
232
|
+
[pjcode, appendToken, markDone],
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
reviewPullRequest,
|
|
237
|
+
setNextActionDate,
|
|
238
|
+
setStory,
|
|
239
|
+
setStatus,
|
|
240
|
+
setInTmuxByHuman,
|
|
241
|
+
closeIssue,
|
|
242
|
+
};
|
|
243
|
+
};
|
package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOverlay.test.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { act, renderHook } from '@testing-library/react';
|
|
2
|
+
import { overlayStorageKey } from '../overlay';
|
|
3
|
+
import { useConsoleOverlay } from './useConsoleOverlay';
|
|
4
|
+
|
|
5
|
+
describe('useConsoleOverlay', () => {
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
localStorage.clear();
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('starts from the persisted overlay', () => {
|
|
11
|
+
localStorage.setItem(
|
|
12
|
+
overlayStorageKey('umino'),
|
|
13
|
+
JSON.stringify({ PVTI_1: { ts: 5, mode: 'prs', done: true } }),
|
|
14
|
+
);
|
|
15
|
+
const { result } = renderHook(() => useConsoleOverlay('umino'));
|
|
16
|
+
expect(result.current.overlay.PVTI_1?.done).toBe(true);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('patches and persists overlay entries with timestamp and mode', () => {
|
|
20
|
+
const { result } = renderHook(() => useConsoleOverlay('umino'));
|
|
21
|
+
act(() => {
|
|
22
|
+
result.current.patchOverlay('PVTI_2', { done: true }, 'triage');
|
|
23
|
+
});
|
|
24
|
+
expect(result.current.overlay.PVTI_2?.done).toBe(true);
|
|
25
|
+
expect(result.current.overlay.PVTI_2?.mode).toBe('triage');
|
|
26
|
+
const stored = JSON.parse(
|
|
27
|
+
localStorage.getItem(overlayStorageKey('umino')) ?? '{}',
|
|
28
|
+
);
|
|
29
|
+
expect(stored.PVTI_2.done).toBe(true);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('ignores malformed persisted entries', () => {
|
|
33
|
+
localStorage.setItem(
|
|
34
|
+
overlayStorageKey('umino'),
|
|
35
|
+
JSON.stringify({ bad: { no: 'ts' } }),
|
|
36
|
+
);
|
|
37
|
+
const { result } = renderHook(() => useConsoleOverlay('umino'));
|
|
38
|
+
expect(result.current.overlay.bad).toBeUndefined();
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { useCallback, useState } from 'react';
|
|
2
|
+
import { overlayStorageKey, writeOverlayEntry } from '../overlay';
|
|
3
|
+
import type {
|
|
4
|
+
ConsoleOverlay,
|
|
5
|
+
ConsoleOverlayEntry,
|
|
6
|
+
ConsoleTabName,
|
|
7
|
+
} from '../types';
|
|
8
|
+
|
|
9
|
+
const isOverlayEntry = (value: unknown): value is ConsoleOverlayEntry =>
|
|
10
|
+
value !== null &&
|
|
11
|
+
typeof value === 'object' &&
|
|
12
|
+
typeof (value as { ts?: unknown }).ts === 'number';
|
|
13
|
+
|
|
14
|
+
const readOverlay = (pjcode: string): ConsoleOverlay => {
|
|
15
|
+
if (typeof localStorage === 'undefined') {
|
|
16
|
+
return {};
|
|
17
|
+
}
|
|
18
|
+
const raw = localStorage.getItem(overlayStorageKey(pjcode));
|
|
19
|
+
if (raw === null) {
|
|
20
|
+
return {};
|
|
21
|
+
}
|
|
22
|
+
const parsed: unknown = JSON.parse(raw);
|
|
23
|
+
if (parsed === null || typeof parsed !== 'object') {
|
|
24
|
+
return {};
|
|
25
|
+
}
|
|
26
|
+
const overlay: ConsoleOverlay = {};
|
|
27
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
28
|
+
if (isOverlayEntry(value)) {
|
|
29
|
+
overlay[key] = value;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return overlay;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const persistOverlay = (pjcode: string, overlay: ConsoleOverlay): void => {
|
|
36
|
+
if (typeof localStorage !== 'undefined') {
|
|
37
|
+
localStorage.setItem(overlayStorageKey(pjcode), JSON.stringify(overlay));
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type ConsoleOverlayState = {
|
|
42
|
+
overlay: ConsoleOverlay;
|
|
43
|
+
patchOverlay: (
|
|
44
|
+
key: string,
|
|
45
|
+
patch: Partial<Omit<ConsoleOverlayEntry, 'ts' | 'mode'>>,
|
|
46
|
+
mode: ConsoleTabName,
|
|
47
|
+
) => void;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const useConsoleOverlay = (pjcode: string): ConsoleOverlayState => {
|
|
51
|
+
const [overlay, setOverlay] = useState<ConsoleOverlay>(() =>
|
|
52
|
+
readOverlay(pjcode),
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const patchOverlay = useCallback(
|
|
56
|
+
(
|
|
57
|
+
key: string,
|
|
58
|
+
patch: Partial<Omit<ConsoleOverlayEntry, 'ts' | 'mode'>>,
|
|
59
|
+
mode: ConsoleTabName,
|
|
60
|
+
) => {
|
|
61
|
+
setOverlay((current) => {
|
|
62
|
+
const next = writeOverlayEntry(current, key, patch, mode, Date.now());
|
|
63
|
+
persistOverlay(pjcode, next);
|
|
64
|
+
return next;
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
[pjcode],
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
return { overlay, patchOverlay };
|
|
71
|
+
};
|
package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsolePjcode.test.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { parsePjcodeFromPath } from './useConsolePjcode';
|
|
2
|
+
|
|
3
|
+
describe('parsePjcodeFromPath', () => {
|
|
4
|
+
it('extracts the pjcode from a projects path', () => {
|
|
5
|
+
expect(parsePjcodeFromPath('/projects/umino')).toBe('umino');
|
|
6
|
+
expect(parsePjcodeFromPath('/projects/umino/prs')).toBe('umino');
|
|
7
|
+
expect(parsePjcodeFromPath('/projects/xmile/triage')).toBe('xmile');
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('tolerates a trailing slash', () => {
|
|
11
|
+
expect(parsePjcodeFromPath('/projects/utage3/')).toBe('utage3');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('returns null when the path is not under projects', () => {
|
|
15
|
+
expect(parsePjcodeFromPath('/')).toBeNull();
|
|
16
|
+
expect(parsePjcodeFromPath('/index.html')).toBeNull();
|
|
17
|
+
expect(parsePjcodeFromPath('/assets/app.js')).toBeNull();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('returns null when no pjcode segment follows projects', () => {
|
|
21
|
+
expect(parsePjcodeFromPath('/projects')).toBeNull();
|
|
22
|
+
expect(parsePjcodeFromPath('/projects/')).toBeNull();
|
|
23
|
+
});
|
|
24
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const parsePjcodeFromPath = (pathname: string): string | null => {
|
|
2
|
+
const segments = pathname.split('/').filter((segment) => segment.length > 0);
|
|
3
|
+
if (segments.length < 2 || segments[0] !== 'projects') {
|
|
4
|
+
return null;
|
|
5
|
+
}
|
|
6
|
+
const pjcode = segments[1];
|
|
7
|
+
if (pjcode.length === 0) {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
return pjcode;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const useConsolePjcode = (): string | null => {
|
|
14
|
+
const pathname =
|
|
15
|
+
typeof window === 'undefined' ? '' : window.location.pathname;
|
|
16
|
+
return parsePjcodeFromPath(pathname);
|
|
17
|
+
};
|
package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleResource.test.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { renderHook, waitFor } from '@testing-library/react';
|
|
2
|
+
import { ResourceCache } from '../lib/resourceCache';
|
|
3
|
+
import { useConsoleResource } from './useConsoleResource';
|
|
4
|
+
|
|
5
|
+
describe('useConsoleResource', () => {
|
|
6
|
+
it('loads the resource and exposes loading then data', async () => {
|
|
7
|
+
const cache = new ResourceCache<string>(async () => 'loaded');
|
|
8
|
+
const { result } = renderHook(() =>
|
|
9
|
+
useConsoleResource(cache, 'k', 'u', 'fallback'),
|
|
10
|
+
);
|
|
11
|
+
expect(result.current.isLoading).toBe(true);
|
|
12
|
+
expect(result.current.data).toBe('fallback');
|
|
13
|
+
await waitFor(() => {
|
|
14
|
+
expect(result.current.isLoading).toBe(false);
|
|
15
|
+
});
|
|
16
|
+
expect(result.current.data).toBe('loaded');
|
|
17
|
+
expect(result.current.error).toBeNull();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('stays idle with the fallback when the key is null', () => {
|
|
21
|
+
const cache = new ResourceCache<string>(async () => 'loaded');
|
|
22
|
+
const { result } = renderHook(() =>
|
|
23
|
+
useConsoleResource(cache, null, null, 'fallback'),
|
|
24
|
+
);
|
|
25
|
+
expect(result.current.isLoading).toBe(false);
|
|
26
|
+
expect(result.current.data).toBe('fallback');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('surfaces a fetch error', async () => {
|
|
30
|
+
const cache = new ResourceCache<string>(async () => {
|
|
31
|
+
throw new Error('boom');
|
|
32
|
+
});
|
|
33
|
+
const { result } = renderHook(() =>
|
|
34
|
+
useConsoleResource(cache, 'k', 'u', 'fallback'),
|
|
35
|
+
);
|
|
36
|
+
await waitFor(() => {
|
|
37
|
+
expect(result.current.error).toBe('boom');
|
|
38
|
+
});
|
|
39
|
+
expect(result.current.isLoading).toBe(false);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import type { ResourceCache } from '../lib/resourceCache';
|
|
3
|
+
|
|
4
|
+
export type ConsoleResourceState<T> = {
|
|
5
|
+
data: T;
|
|
6
|
+
isLoading: boolean;
|
|
7
|
+
error: string | null;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const useConsoleResource = <T>(
|
|
11
|
+
cache: ResourceCache<T>,
|
|
12
|
+
key: string | null,
|
|
13
|
+
url: string | null,
|
|
14
|
+
fallback: T,
|
|
15
|
+
): ConsoleResourceState<T> => {
|
|
16
|
+
const cached = key !== null ? cache.peek(key) : undefined;
|
|
17
|
+
const [data, setData] = useState<T>(cached ?? fallback);
|
|
18
|
+
const [isLoading, setIsLoading] = useState<boolean>(
|
|
19
|
+
key !== null && cached === undefined,
|
|
20
|
+
);
|
|
21
|
+
const [error, setError] = useState<string | null>(null);
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (key === null || url === null) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const existing = cache.peek(key);
|
|
28
|
+
if (existing !== undefined) {
|
|
29
|
+
setData(existing);
|
|
30
|
+
setIsLoading(false);
|
|
31
|
+
setError(null);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
let cancelled = false;
|
|
35
|
+
setIsLoading(true);
|
|
36
|
+
setError(null);
|
|
37
|
+
cache
|
|
38
|
+
.load(key, url)
|
|
39
|
+
.then((value) => {
|
|
40
|
+
if (!cancelled) {
|
|
41
|
+
setData(value);
|
|
42
|
+
setIsLoading(false);
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
.catch((cause: unknown) => {
|
|
46
|
+
if (!cancelled) {
|
|
47
|
+
setError(cause instanceof Error ? cause.message : String(cause));
|
|
48
|
+
setIsLoading(false);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
return () => {
|
|
52
|
+
cancelled = true;
|
|
53
|
+
};
|
|
54
|
+
}, [cache, key, url]);
|
|
55
|
+
|
|
56
|
+
return { data, isLoading, error };
|
|
57
|
+
};
|
package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTabData.test.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { renderHook, waitFor } from '@testing-library/react';
|
|
2
|
+
import { useConsoleTabData } from './useConsoleTabData';
|
|
3
|
+
|
|
4
|
+
describe('useConsoleTabData', () => {
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
localStorage.clear();
|
|
7
|
+
window.history.replaceState({}, '', '/?k=token');
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('fetches every tab once at startup and parses snapshots', async () => {
|
|
11
|
+
const fetchMock = jest.fn(async (url: string) => ({
|
|
12
|
+
ok: true,
|
|
13
|
+
status: 200,
|
|
14
|
+
json: async () => ({
|
|
15
|
+
pjcode: 'umino',
|
|
16
|
+
generatedAt: '2026-06-19T00:00:00.000Z',
|
|
17
|
+
statusOptions: [{ id: 's1', name: 'Unread', color: 'ORANGE' }],
|
|
18
|
+
storyColors: {},
|
|
19
|
+
items: url.includes('/prs/')
|
|
20
|
+
? [{ number: 1, itemId: 'PVTI_1', projectItemId: 'PVTI_1' }]
|
|
21
|
+
: [],
|
|
22
|
+
}),
|
|
23
|
+
}));
|
|
24
|
+
global.fetch = fetchMock as unknown as typeof fetch;
|
|
25
|
+
|
|
26
|
+
const { result } = renderHook(() => useConsoleTabData('umino'));
|
|
27
|
+
await waitFor(() => {
|
|
28
|
+
expect(result.current.isLoading).toBe(false);
|
|
29
|
+
});
|
|
30
|
+
expect(fetchMock).toHaveBeenCalledTimes(5);
|
|
31
|
+
expect(fetchMock).toHaveBeenCalledWith(
|
|
32
|
+
expect.stringContaining('/projects/umino/prs/list.json'),
|
|
33
|
+
);
|
|
34
|
+
expect(result.current.snapshots.prs?.items.length).toBe(1);
|
|
35
|
+
expect(result.current.snapshots.prs?.generatedAt).toBe(
|
|
36
|
+
'2026-06-19T00:00:00.000Z',
|
|
37
|
+
);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('surfaces an error when a tab fetch fails', async () => {
|
|
41
|
+
const fetchMock = jest.fn(async () => ({
|
|
42
|
+
ok: false,
|
|
43
|
+
status: 500,
|
|
44
|
+
json: async () => ({}),
|
|
45
|
+
}));
|
|
46
|
+
global.fetch = fetchMock as unknown as typeof fetch;
|
|
47
|
+
const { result } = renderHook(() => useConsoleTabData('umino'));
|
|
48
|
+
await waitFor(() => {
|
|
49
|
+
expect(result.current.error).toBe('HTTP 500');
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('reports an error and fetches nothing when no pjcode is in the URL', async () => {
|
|
54
|
+
const fetchMock = jest.fn();
|
|
55
|
+
global.fetch = fetchMock as unknown as typeof fetch;
|
|
56
|
+
const { result } = renderHook(() => useConsoleTabData(null));
|
|
57
|
+
await waitFor(() => {
|
|
58
|
+
expect(result.current.isLoading).toBe(false);
|
|
59
|
+
});
|
|
60
|
+
expect(result.current.error).toBe('No project specified in the URL path.');
|
|
61
|
+
expect(fetchMock).not.toHaveBeenCalled();
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import type {
|
|
3
|
+
ConsoleFieldOption,
|
|
4
|
+
ConsoleListItem,
|
|
5
|
+
ConsoleStoryColorSource,
|
|
6
|
+
ConsoleTabName,
|
|
7
|
+
} from '../types';
|
|
8
|
+
import { CONSOLE_TABS } from '../types';
|
|
9
|
+
import { useConsoleToken } from './useConsoleToken';
|
|
10
|
+
|
|
11
|
+
export type ConsoleTabSnapshot = {
|
|
12
|
+
items: ConsoleListItem[];
|
|
13
|
+
generatedAt: string;
|
|
14
|
+
statusOptions: ConsoleFieldOption[];
|
|
15
|
+
storyOptions: ConsoleFieldOption[];
|
|
16
|
+
storyColors: ConsoleStoryColorSource;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type ConsoleTabDataState = {
|
|
20
|
+
snapshots: Record<ConsoleTabName, ConsoleTabSnapshot | null>;
|
|
21
|
+
isLoading: boolean;
|
|
22
|
+
error: string | null;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
26
|
+
value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
27
|
+
|
|
28
|
+
const parseItems = (payload: unknown): ConsoleListItem[] => {
|
|
29
|
+
if (!isRecord(payload) || !Array.isArray(payload.items)) {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
return payload.items.filter(isRecord) as unknown as ConsoleListItem[];
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const parseOptions = (value: unknown): ConsoleFieldOption[] => {
|
|
36
|
+
if (!Array.isArray(value)) {
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
return value.filter(isRecord) as unknown as ConsoleFieldOption[];
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const parseSnapshot = (payload: unknown): ConsoleTabSnapshot => ({
|
|
43
|
+
items: parseItems(payload),
|
|
44
|
+
generatedAt:
|
|
45
|
+
isRecord(payload) && typeof payload.generatedAt === 'string'
|
|
46
|
+
? payload.generatedAt
|
|
47
|
+
: '',
|
|
48
|
+
statusOptions: isRecord(payload) ? parseOptions(payload.statusOptions) : [],
|
|
49
|
+
storyOptions: isRecord(payload) ? parseOptions(payload.storyOptions) : [],
|
|
50
|
+
storyColors:
|
|
51
|
+
isRecord(payload) && isRecord(payload.storyColors)
|
|
52
|
+
? (payload.storyColors as ConsoleStoryColorSource)
|
|
53
|
+
: {},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const emptySnapshots = (): Record<
|
|
57
|
+
ConsoleTabName,
|
|
58
|
+
ConsoleTabSnapshot | null
|
|
59
|
+
> => {
|
|
60
|
+
const result = {} as Record<ConsoleTabName, ConsoleTabSnapshot | null>;
|
|
61
|
+
for (const tab of CONSOLE_TABS) {
|
|
62
|
+
result[tab.name] = null;
|
|
63
|
+
}
|
|
64
|
+
return result;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const buildListUrl = (pjcode: string, tab: ConsoleTabName): string =>
|
|
68
|
+
`/projects/${pjcode}/${tab}/list.json`;
|
|
69
|
+
|
|
70
|
+
export const useConsoleTabData = (
|
|
71
|
+
pjcode: string | null,
|
|
72
|
+
): ConsoleTabDataState => {
|
|
73
|
+
const { appendToken } = useConsoleToken();
|
|
74
|
+
const [snapshots, setSnapshots] =
|
|
75
|
+
useState<Record<ConsoleTabName, ConsoleTabSnapshot | null>>(emptySnapshots);
|
|
76
|
+
const [isLoading, setIsLoading] = useState<boolean>(true);
|
|
77
|
+
const [error, setError] = useState<string | null>(null);
|
|
78
|
+
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
let cancelled = false;
|
|
81
|
+
setIsLoading(true);
|
|
82
|
+
setError(null);
|
|
83
|
+
|
|
84
|
+
if (pjcode === null) {
|
|
85
|
+
setSnapshots(emptySnapshots());
|
|
86
|
+
setIsLoading(false);
|
|
87
|
+
setError('No project specified in the URL path.');
|
|
88
|
+
return () => {
|
|
89
|
+
cancelled = true;
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
Promise.all(
|
|
94
|
+
CONSOLE_TABS.map(async (tab) => {
|
|
95
|
+
const url = appendToken(buildListUrl(pjcode, tab.name));
|
|
96
|
+
const response = await fetch(url);
|
|
97
|
+
if (!response.ok) {
|
|
98
|
+
throw new Error(`HTTP ${response.status}`);
|
|
99
|
+
}
|
|
100
|
+
const payload: unknown = await response.json();
|
|
101
|
+
return [tab.name, parseSnapshot(payload)] as const;
|
|
102
|
+
}),
|
|
103
|
+
)
|
|
104
|
+
.then((entries) => {
|
|
105
|
+
if (cancelled) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const next = emptySnapshots();
|
|
109
|
+
for (const [name, snapshot] of entries) {
|
|
110
|
+
next[name] = snapshot;
|
|
111
|
+
}
|
|
112
|
+
setSnapshots(next);
|
|
113
|
+
setIsLoading(false);
|
|
114
|
+
})
|
|
115
|
+
.catch((cause: unknown) => {
|
|
116
|
+
if (cancelled) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
setError(cause instanceof Error ? cause.message : String(cause));
|
|
120
|
+
setIsLoading(false);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
return () => {
|
|
124
|
+
cancelled = true;
|
|
125
|
+
};
|
|
126
|
+
}, [pjcode, appendToken]);
|
|
127
|
+
|
|
128
|
+
return { snapshots, isLoading, error };
|
|
129
|
+
};
|