github-issue-tower-defence-management 1.90.0 → 1.91.1

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.
Files changed (174) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +19 -5
  3. package/bin/adapter/entry-points/cli/index.js +17 -13
  4. package/bin/adapter/entry-points/cli/index.js.map +1 -1
  5. package/bin/adapter/entry-points/cli/projectConfig.js +2 -0
  6. package/bin/adapter/entry-points/cli/projectConfig.js.map +1 -1
  7. package/bin/adapter/entry-points/console/consoleOperationApi.js +54 -27
  8. package/bin/adapter/entry-points/console/consoleOperationApi.js.map +1 -1
  9. package/bin/adapter/entry-points/console/consoleProjectResolver.js +38 -0
  10. package/bin/adapter/entry-points/console/consoleProjectResolver.js.map +1 -0
  11. package/bin/adapter/entry-points/console/consoleServer.js +3 -4
  12. package/bin/adapter/entry-points/console/consoleServer.js.map +1 -1
  13. package/bin/adapter/entry-points/console/ui-dist/assets/index-BU6p3cGU.css +1 -0
  14. package/bin/adapter/entry-points/console/ui-dist/assets/index-BvuSQN9s.js +100 -0
  15. package/bin/adapter/entry-points/console/ui-dist/index.html +2 -2
  16. package/bin/adapter/entry-points/handlers/consoleListsWriter.js +1 -0
  17. package/bin/adapter/entry-points/handlers/consoleListsWriter.js.map +1 -1
  18. package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js +16 -0
  19. package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js.map +1 -1
  20. package/bin/domain/usecases/console/GenerateConsoleListsUseCase.js +3 -0
  21. package/bin/domain/usecases/console/GenerateConsoleListsUseCase.js.map +1 -1
  22. package/jest.config.js +57 -9
  23. package/package.json +17 -13
  24. package/src/adapter/entry-points/cli/index.test.ts +18 -3
  25. package/src/adapter/entry-points/cli/index.ts +32 -14
  26. package/src/adapter/entry-points/cli/projectConfig.ts +3 -0
  27. package/src/adapter/entry-points/console/consoleOperationApi.test.ts +129 -15
  28. package/src/adapter/entry-points/console/consoleOperationApi.ts +83 -28
  29. package/src/adapter/entry-points/console/consoleProjectResolver.test.ts +96 -0
  30. package/src/adapter/entry-points/console/consoleProjectResolver.ts +50 -0
  31. package/src/adapter/entry-points/console/consoleServer.test.ts +5 -4
  32. package/src/adapter/entry-points/console/consoleServer.ts +5 -7
  33. package/src/adapter/entry-points/console/ui/jest.setup.ts +1 -0
  34. package/src/adapter/entry-points/console/ui/jest.styleMock.js +1 -0
  35. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMarkdownContent.stories.tsx +27 -0
  36. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMarkdownContent.test.tsx +36 -0
  37. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMarkdownContent.tsx +50 -0
  38. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMermaidDiagram.stories.tsx +22 -0
  39. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMermaidDiagram.test.tsx +38 -0
  40. package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMermaidDiagram.tsx +65 -0
  41. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleChangedFileList.stories.tsx +28 -0
  42. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleChangedFileList.test.tsx +42 -0
  43. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleChangedFileList.tsx +55 -0
  44. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommentList.stories.tsx +29 -0
  45. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommentList.test.tsx +55 -0
  46. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommentList.tsx +66 -0
  47. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommitList.stories.tsx +25 -0
  48. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommitList.test.tsx +53 -0
  49. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommitList.tsx +53 -0
  50. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemDetail.stories.tsx +79 -0
  51. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemDetail.test.tsx +81 -0
  52. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemDetail.tsx +229 -0
  53. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemIcon.stories.tsx +82 -0
  54. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemIcon.test.tsx +52 -0
  55. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemIcon.tsx +32 -0
  56. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestDetail.stories.tsx +31 -0
  57. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestDetail.test.tsx +40 -0
  58. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestDetail.tsx +88 -0
  59. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsolePanel.stories.tsx +26 -0
  60. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsolePanel.test.tsx +32 -0
  61. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsolePanel.tsx +36 -0
  62. package/src/adapter/entry-points/console/ui/src/features/console/components/{ConsoleProjectHeader.stories.tsx → layout/ConsoleProjectSummary.stories.tsx} +5 -5
  63. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleProjectSummary.test.tsx +14 -0
  64. package/src/adapter/entry-points/console/ui/src/features/console/components/{ConsoleProjectHeader.tsx → layout/ConsoleProjectSummary.tsx} +3 -1
  65. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.stories.tsx +70 -0
  66. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.test.tsx +59 -0
  67. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.tsx +41 -0
  68. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemList.stories.tsx +60 -0
  69. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemList.test.tsx +87 -0
  70. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemList.tsx +68 -0
  71. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemSummary.stories.tsx +25 -0
  72. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemSummary.test.tsx +43 -0
  73. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemSummary.tsx +34 -0
  74. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStorySummary.stories.tsx +27 -0
  75. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStorySummary.test.tsx +24 -0
  76. package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStorySummary.tsx +28 -0
  77. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleCloseActions.stories.tsx +14 -0
  78. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleCloseActions.test.tsx +21 -0
  79. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleCloseActions.tsx +26 -0
  80. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleNextActionDateActions.stories.tsx +20 -0
  81. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleNextActionDateActions.test.tsx +42 -0
  82. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleNextActionDateActions.tsx +28 -0
  83. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleOperationMenu.stories.tsx +55 -0
  84. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleOperationMenu.test.tsx +85 -0
  85. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleOperationMenu.tsx +58 -0
  86. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsolePullRequestReviewActions.stories.tsx +14 -0
  87. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsolePullRequestReviewActions.test.tsx +33 -0
  88. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsolePullRequestReviewActions.tsx +34 -0
  89. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleStatusActions.stories.tsx +17 -0
  90. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleStatusActions.test.tsx +49 -0
  91. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleStatusActions.tsx +66 -0
  92. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleStoryActions.stories.tsx +17 -0
  93. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleStoryActions.test.tsx +39 -0
  94. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleStoryActions.tsx +42 -0
  95. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleCaches.test.ts +22 -0
  96. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleCaches.ts +42 -0
  97. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleItemDetailData.test.ts +126 -0
  98. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleItemDetailData.ts +167 -0
  99. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOperations.test.ts +198 -0
  100. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOperations.ts +243 -0
  101. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOverlay.test.ts +40 -0
  102. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOverlay.ts +71 -0
  103. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleResource.test.ts +41 -0
  104. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleResource.ts +57 -0
  105. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTabData.test.ts +63 -0
  106. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTabData.ts +129 -0
  107. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleToken.test.ts +41 -0
  108. package/src/adapter/entry-points/console/ui/src/features/console/lib/consoleApi.test.ts +155 -0
  109. package/src/adapter/entry-points/console/ui/src/features/console/lib/consoleApi.ts +187 -0
  110. package/src/adapter/entry-points/console/ui/src/features/console/lib/markdown.test.ts +76 -0
  111. package/src/adapter/entry-points/console/ui/src/features/console/lib/markdown.ts +73 -0
  112. package/src/adapter/entry-points/console/ui/src/features/console/lib/mermaidLoader.test.ts +27 -0
  113. package/src/adapter/entry-points/console/ui/src/features/console/lib/mermaidLoader.ts +71 -0
  114. package/src/adapter/entry-points/console/ui/src/features/console/lib/resourceCache.test.ts +56 -0
  115. package/src/adapter/entry-points/console/ui/src/features/console/lib/resourceCache.ts +51 -0
  116. package/src/adapter/entry-points/console/ui/src/features/console/logic/colors.test.ts +34 -0
  117. package/src/adapter/entry-points/console/ui/src/features/console/logic/colors.ts +73 -0
  118. package/src/adapter/entry-points/console/ui/src/features/console/logic/fileStatus.test.ts +35 -0
  119. package/src/adapter/entry-points/console/ui/src/features/console/logic/fileStatus.ts +21 -0
  120. package/src/adapter/entry-points/console/ui/src/features/console/logic/grouping.test.ts +91 -0
  121. package/src/adapter/entry-points/console/ui/src/features/console/logic/grouping.ts +79 -0
  122. package/src/adapter/entry-points/console/ui/src/features/console/logic/itemIcons.test.ts +97 -0
  123. package/src/adapter/entry-points/console/ui/src/features/console/logic/itemIcons.ts +95 -0
  124. package/src/adapter/entry-points/console/ui/src/features/console/logic/operations.test.ts +37 -0
  125. package/src/adapter/entry-points/console/ui/src/features/console/logic/operations.ts +35 -0
  126. package/src/adapter/entry-points/console/ui/src/features/console/logic/overlay.test.ts +124 -0
  127. package/src/adapter/entry-points/console/ui/src/features/console/logic/overlay.ts +101 -0
  128. package/src/adapter/entry-points/console/ui/src/features/console/logic/relativeTime.test.ts +52 -0
  129. package/src/adapter/entry-points/console/ui/src/features/console/logic/relativeTime.ts +51 -0
  130. package/src/adapter/entry-points/console/ui/src/features/console/logic/types.ts +141 -0
  131. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsoleItemDetailContainer.test.tsx +79 -0
  132. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsoleItemDetailContainer.tsx +109 -0
  133. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.test.tsx +74 -0
  134. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.tsx +137 -11
  135. package/src/adapter/entry-points/console/ui/src/features/console/testing/fixtures.ts +244 -0
  136. package/src/adapter/entry-points/console/ui/src/index.css +352 -2
  137. package/src/adapter/entry-points/console/ui/tsconfig.json +1 -0
  138. package/src/adapter/entry-points/console/ui/vite.config.ts +5 -0
  139. package/src/adapter/entry-points/console/ui-dist/assets/index-BU6p3cGU.css +1 -0
  140. package/src/adapter/entry-points/console/ui-dist/assets/index-PtVrAcBb.js +100 -0
  141. package/src/adapter/entry-points/console/ui-dist/index.html +2 -2
  142. package/src/adapter/entry-points/handlers/consoleListsWriter.test.ts +27 -2
  143. package/src/adapter/entry-points/handlers/consoleListsWriter.ts +1 -0
  144. package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts +25 -0
  145. package/src/domain/usecases/adapter-interfaces/IssueRepository.ts +4 -0
  146. package/src/domain/usecases/console/GenerateConsoleListsUseCase.test.ts +26 -0
  147. package/src/domain/usecases/console/GenerateConsoleListsUseCase.ts +17 -1
  148. package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
  149. package/types/adapter/entry-points/cli/projectConfig.d.ts +1 -0
  150. package/types/adapter/entry-points/cli/projectConfig.d.ts.map +1 -1
  151. package/types/adapter/entry-points/console/consoleOperationApi.d.ts +6 -2
  152. package/types/adapter/entry-points/console/consoleOperationApi.d.ts.map +1 -1
  153. package/types/adapter/entry-points/console/consoleProjectResolver.d.ts +6 -0
  154. package/types/adapter/entry-points/console/consoleProjectResolver.d.ts.map +1 -0
  155. package/types/adapter/entry-points/console/consoleServer.d.ts +2 -3
  156. package/types/adapter/entry-points/console/consoleServer.d.ts.map +1 -1
  157. package/types/adapter/entry-points/handlers/consoleListsWriter.d.ts.map +1 -1
  158. package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts +1 -0
  159. package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts.map +1 -1
  160. package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts +1 -0
  161. package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts.map +1 -1
  162. package/types/domain/usecases/console/GenerateConsoleListsUseCase.d.ts +2 -1
  163. package/types/domain/usecases/console/GenerateConsoleListsUseCase.d.ts.map +1 -1
  164. package/bin/adapter/entry-points/console/ui-dist/assets/index-DDjYPXRT.js +0 -49
  165. package/bin/adapter/entry-points/console/ui-dist/assets/index-DHlBLm7d.css +0 -1
  166. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleListView.stories.tsx +0 -44
  167. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleListView.tsx +0 -58
  168. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleTabBar.stories.tsx +0 -34
  169. package/src/adapter/entry-points/console/ui/src/features/console/components/ConsoleTabBar.tsx +0 -32
  170. package/src/adapter/entry-points/console/ui/src/features/console/fixtures.ts +0 -47
  171. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleList.ts +0 -78
  172. package/src/adapter/entry-points/console/ui/src/features/console/types.ts +0 -69
  173. package/src/adapter/entry-points/console/ui-dist/assets/index-DDjYPXRT.js +0 -49
  174. package/src/adapter/entry-points/console/ui-dist/assets/index-DHlBLm7d.css +0 -1
@@ -0,0 +1,167 @@
1
+ import { useEffect, useState } from 'react';
2
+ import type { ConsoleRelatedPullRequestView } from '../components/detail/ConsoleItemDetail';
3
+ import type {
4
+ ConsoleChangedFile,
5
+ ConsoleComment,
6
+ ConsoleCommit,
7
+ ConsoleIssueState,
8
+ ConsoleListItem,
9
+ ConsoleRelatedPullRequest,
10
+ } from '../logic/types';
11
+ import type { ConsoleCaches } from './useConsoleCaches';
12
+ import { useConsoleResource } from './useConsoleResource';
13
+
14
+ const EMPTY_COMMENTS: ConsoleComment[] = [];
15
+ const EMPTY_FILES: ConsoleChangedFile[] = [];
16
+ const EMPTY_COMMITS: ConsoleCommit[] = [];
17
+ const EMPTY_RELATED: ConsoleRelatedPullRequest[] = [];
18
+ const DEFAULT_STATE: ConsoleIssueState = {
19
+ state: 'open',
20
+ merged: false,
21
+ isPullRequest: false,
22
+ };
23
+
24
+ export type ConsoleItemDetailData = {
25
+ state: ConsoleIssueState | null;
26
+ body: string;
27
+ bodyIsLoading: boolean;
28
+ bodyError: string | null;
29
+ comments: ConsoleComment[];
30
+ commentsAreLoading: boolean;
31
+ commentsError: string | null;
32
+ files: ConsoleChangedFile[];
33
+ filesAreLoading: boolean;
34
+ filesError: string | null;
35
+ commits: ConsoleCommit[];
36
+ commitsAreLoading: boolean;
37
+ commitsError: string | null;
38
+ relatedPullRequests: ConsoleRelatedPullRequestView[];
39
+ };
40
+
41
+ export const useConsoleItemDetailData = (
42
+ caches: ConsoleCaches,
43
+ item: ConsoleListItem | null,
44
+ ): ConsoleItemDetailData => {
45
+ const key = item !== null ? `${item.repo}#${item.number}` : null;
46
+ const url = item !== null ? item.url : null;
47
+ const isPr = item?.isPr ?? false;
48
+
49
+ const body = useConsoleResource(caches.body, key, url, '');
50
+ const state = useConsoleResource(caches.state, key, url, DEFAULT_STATE);
51
+ const comments = useConsoleResource(
52
+ caches.comments,
53
+ key,
54
+ url,
55
+ EMPTY_COMMENTS,
56
+ );
57
+ const files = useConsoleResource(
58
+ caches.files,
59
+ isPr ? key : null,
60
+ isPr ? url : null,
61
+ EMPTY_FILES,
62
+ );
63
+ const commits = useConsoleResource(
64
+ caches.commits,
65
+ isPr ? key : null,
66
+ isPr ? url : null,
67
+ EMPTY_COMMITS,
68
+ );
69
+ const relatedPrs = useConsoleResource(
70
+ caches.relatedPrs,
71
+ !isPr ? key : null,
72
+ !isPr ? url : null,
73
+ EMPTY_RELATED,
74
+ );
75
+
76
+ const [relatedViews, setRelatedViews] = useState<
77
+ ConsoleRelatedPullRequestView[]
78
+ >([]);
79
+
80
+ useEffect(() => {
81
+ if (isPr || relatedPrs.data.length === 0) {
82
+ setRelatedViews([]);
83
+ return;
84
+ }
85
+ let cancelled = false;
86
+ const initial: ConsoleRelatedPullRequestView[] = relatedPrs.data.map(
87
+ (pullRequest) => ({
88
+ pullRequest,
89
+ files: EMPTY_FILES,
90
+ filesAreLoading: true,
91
+ filesError: null,
92
+ commits: EMPTY_COMMITS,
93
+ commitsAreLoading: true,
94
+ commitsError: null,
95
+ }),
96
+ );
97
+ setRelatedViews(initial);
98
+
99
+ const updateView = (
100
+ prUrl: string,
101
+ patch: Partial<ConsoleRelatedPullRequestView>,
102
+ ): void => {
103
+ if (cancelled) {
104
+ return;
105
+ }
106
+ setRelatedViews((current) =>
107
+ current.map((view) =>
108
+ view.pullRequest.url === prUrl ? { ...view, ...patch } : view,
109
+ ),
110
+ );
111
+ };
112
+
113
+ for (const pullRequest of relatedPrs.data) {
114
+ const relatedKey = pullRequest.url;
115
+ caches.files
116
+ .load(relatedKey, pullRequest.url)
117
+ .then((value) =>
118
+ updateView(pullRequest.url, {
119
+ files: value,
120
+ filesAreLoading: false,
121
+ }),
122
+ )
123
+ .catch((cause: unknown) =>
124
+ updateView(pullRequest.url, {
125
+ filesAreLoading: false,
126
+ filesError: cause instanceof Error ? cause.message : String(cause),
127
+ }),
128
+ );
129
+ caches.commits
130
+ .load(relatedKey, pullRequest.url)
131
+ .then((value) =>
132
+ updateView(pullRequest.url, {
133
+ commits: value,
134
+ commitsAreLoading: false,
135
+ }),
136
+ )
137
+ .catch((cause: unknown) =>
138
+ updateView(pullRequest.url, {
139
+ commitsAreLoading: false,
140
+ commitsError:
141
+ cause instanceof Error ? cause.message : String(cause),
142
+ }),
143
+ );
144
+ }
145
+
146
+ return () => {
147
+ cancelled = true;
148
+ };
149
+ }, [caches, isPr, relatedPrs.data]);
150
+
151
+ return {
152
+ state: state.data,
153
+ body: body.data,
154
+ bodyIsLoading: body.isLoading,
155
+ bodyError: body.error,
156
+ comments: comments.data,
157
+ commentsAreLoading: comments.isLoading,
158
+ commentsError: comments.error,
159
+ files: files.data,
160
+ filesAreLoading: files.isLoading,
161
+ filesError: files.error,
162
+ commits: commits.data,
163
+ commitsAreLoading: commits.isLoading,
164
+ commitsError: commits.error,
165
+ relatedPullRequests: relatedViews,
166
+ };
167
+ };
@@ -0,0 +1,198 @@
1
+ import { act, renderHook } from '@testing-library/react';
2
+ import { overlayStorageKey } from '../logic/overlay';
3
+ import {
4
+ consoleListItemsFixture,
5
+ consoleStatusOptionsFixture,
6
+ } from '../testing/fixtures';
7
+ import { useConsoleOperations } from './useConsoleOperations';
8
+ import { useConsoleOverlay } from './useConsoleOverlay';
9
+
10
+ const prItem = consoleListItemsFixture[0];
11
+ const issueItem = consoleListItemsFixture[2];
12
+
13
+ const captureFetch = (): jest.Mock => {
14
+ const fetchMock = jest.fn(async () => ({
15
+ ok: true,
16
+ status: 200,
17
+ json: async () => ({ ok: true }),
18
+ }));
19
+ global.fetch = fetchMock as unknown as typeof fetch;
20
+ return fetchMock;
21
+ };
22
+
23
+ const lastBody = (fetchMock: jest.Mock): Record<string, unknown> =>
24
+ JSON.parse((fetchMock.mock.calls.at(-1)?.[1] as { body: string }).body);
25
+
26
+ const setup = () => {
27
+ localStorage.clear();
28
+ window.history.replaceState({}, '', '/projects/umino/prs?k=token');
29
+ return renderHook(() => {
30
+ const overlay = useConsoleOverlay('umino');
31
+ const operations = useConsoleOperations('umino', 'prs', overlay);
32
+ return { overlay, operations };
33
+ });
34
+ };
35
+
36
+ describe('useConsoleOperations', () => {
37
+ it('posts a totally wrong review as a close with the totally wrong comment body', async () => {
38
+ const fetchMock = captureFetch();
39
+ const { result } = setup();
40
+ await act(async () => {
41
+ await result.current.operations.reviewPullRequest(
42
+ prItem,
43
+ prItem.url,
44
+ 'totally_wrong',
45
+ );
46
+ });
47
+ expect(fetchMock.mock.calls[0][0]).toBe('/api/review?k=token');
48
+ expect(lastBody(fetchMock)).toMatchObject({
49
+ pjcode: 'umino',
50
+ action: 'close',
51
+ prUrl: prItem.url,
52
+ commentBody: 'totally wrong',
53
+ });
54
+ });
55
+
56
+ it('posts an unnecessary review as a close with the unnecessary comment body', async () => {
57
+ const fetchMock = captureFetch();
58
+ const { result } = setup();
59
+ await act(async () => {
60
+ await result.current.operations.reviewPullRequest(
61
+ prItem,
62
+ prItem.url,
63
+ 'unnecessary',
64
+ );
65
+ });
66
+ expect(lastBody(fetchMock)).toMatchObject({
67
+ action: 'close',
68
+ commentBody: 'This pull request is unnecessary.',
69
+ });
70
+ });
71
+
72
+ it('posts an approve review and marks the item done in the overlay', async () => {
73
+ captureFetch();
74
+ const { result } = setup();
75
+ await act(async () => {
76
+ await result.current.operations.reviewPullRequest(
77
+ prItem,
78
+ prItem.url,
79
+ 'approve',
80
+ );
81
+ });
82
+ const stored = JSON.parse(
83
+ localStorage.getItem(overlayStorageKey('umino')) ?? '{}',
84
+ );
85
+ expect(stored[prItem.projectItemId].done).toBe(true);
86
+ });
87
+
88
+ it('posts a not-planned close through the triage endpoint', async () => {
89
+ const fetchMock = captureFetch();
90
+ const { result } = setup();
91
+ await act(async () => {
92
+ await result.current.operations.closeIssue(
93
+ issueItem,
94
+ 'close_not_planned',
95
+ );
96
+ });
97
+ expect(fetchMock.mock.calls[0][0]).toBe('/api/triage?k=token');
98
+ expect(lastBody(fetchMock)).toMatchObject({
99
+ pjcode: 'umino',
100
+ action: 'close_not_planned',
101
+ issueUrl: issueItem.url,
102
+ });
103
+ });
104
+
105
+ it('posts set_status and records the overlay status', async () => {
106
+ const fetchMock = captureFetch();
107
+ const { result } = setup();
108
+ const option = consoleStatusOptionsFixture[1];
109
+ await act(async () => {
110
+ await result.current.operations.setStatus(issueItem, option);
111
+ });
112
+ expect(lastBody(fetchMock)).toMatchObject({
113
+ action: 'set_status',
114
+ statusName: option.name,
115
+ });
116
+ const stored = JSON.parse(
117
+ localStorage.getItem(overlayStorageKey('umino')) ?? '{}',
118
+ );
119
+ expect(stored[issueItem.projectItemId].status.name).toBe(option.name);
120
+ });
121
+
122
+ it('posts set_intmux through the intmux endpoint', async () => {
123
+ const fetchMock = captureFetch();
124
+ const { result } = setup();
125
+ const option = consoleStatusOptionsFixture[5];
126
+ await act(async () => {
127
+ await result.current.operations.setInTmuxByHuman(issueItem, option);
128
+ });
129
+ expect(fetchMock.mock.calls[0][0]).toBe('/api/intmux?k=token');
130
+ expect(lastBody(fetchMock)).toMatchObject({
131
+ pjcode: 'umino',
132
+ action: 'set_intmux',
133
+ });
134
+ });
135
+
136
+ it('does not mark done on snooze outside the todo-by-human tab', async () => {
137
+ captureFetch();
138
+ const { result } = setup();
139
+ await act(async () => {
140
+ await result.current.operations.setNextActionDate(
141
+ issueItem,
142
+ 'snooze_1day',
143
+ );
144
+ });
145
+ const stored = JSON.parse(
146
+ localStorage.getItem(overlayStorageKey('umino')) ?? '{}',
147
+ );
148
+ expect(stored[issueItem.projectItemId]?.done).toBeUndefined();
149
+ });
150
+
151
+ it('marks done on snooze in the todo-by-human tab so the item is skipped', async () => {
152
+ captureFetch();
153
+ localStorage.clear();
154
+ window.history.replaceState(
155
+ {},
156
+ '',
157
+ '/projects/umino/todo-by-human?k=token',
158
+ );
159
+ const { result } = renderHook(() => {
160
+ const overlay = useConsoleOverlay('umino');
161
+ const operations = useConsoleOperations(
162
+ 'umino',
163
+ 'todo-by-human',
164
+ overlay,
165
+ );
166
+ return { overlay, operations };
167
+ });
168
+ await act(async () => {
169
+ await result.current.operations.setNextActionDate(
170
+ issueItem,
171
+ 'snooze_1week',
172
+ );
173
+ });
174
+ const stored = JSON.parse(
175
+ localStorage.getItem(overlayStorageKey('umino')) ?? '{}',
176
+ );
177
+ expect(stored[issueItem.projectItemId].done).toBe(true);
178
+ });
179
+
180
+ it('rejects an operation and posts nothing when no pjcode is available', async () => {
181
+ const fetchMock = captureFetch();
182
+ localStorage.clear();
183
+ window.history.replaceState({}, '', '/?k=token');
184
+ const { result } = renderHook(() => {
185
+ const overlay = useConsoleOverlay('console');
186
+ const operations = useConsoleOperations(null, 'prs', overlay);
187
+ return { overlay, operations };
188
+ });
189
+ await expect(
190
+ result.current.operations.reviewPullRequest(
191
+ prItem,
192
+ prItem.url,
193
+ 'approve',
194
+ ),
195
+ ).rejects.toThrow('No project specified in the URL path.');
196
+ expect(fetchMock).not.toHaveBeenCalled();
197
+ });
198
+ });
@@ -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 '../logic/operations';
15
+ import { overlayKeyForItem } from '../logic/overlay';
16
+ import type {
17
+ ConsoleFieldOption,
18
+ ConsoleListItem,
19
+ ConsoleTabName,
20
+ } from '../logic/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
+ };
@@ -0,0 +1,40 @@
1
+ import { act, renderHook } from '@testing-library/react';
2
+ import { overlayStorageKey } from '../logic/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
+ });