github-issue-tower-defence-management 1.1.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.
Files changed (221) hide show
  1. package/.env.example +6 -0
  2. package/.eslintrc.cjs +55 -0
  3. package/.github/CODEOWNERS +2 -0
  4. package/.github/workflows/assign-all-cards-to-owner.yml +14 -0
  5. package/.github/workflows/commit-lint.yml +54 -0
  6. package/.github/workflows/configs/commitlint.config.js +27 -0
  7. package/.github/workflows/create-pr.yml +64 -0
  8. package/.github/workflows/format.yml +25 -0
  9. package/.github/workflows/publish.yml +47 -0
  10. package/.github/workflows/test.yml +45 -0
  11. package/.github/workflows/umino-project.yml +181 -0
  12. package/.prettierignore +22 -0
  13. package/.prettierrc +5 -0
  14. package/CHANGELOG.md +49 -0
  15. package/CONTRIBUTING.md +107 -0
  16. package/README.md +108 -0
  17. package/bin/adapter/entry-points/cli/index.js +26 -0
  18. package/bin/adapter/entry-points/cli/index.js.map +1 -0
  19. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +142 -0
  20. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -0
  21. package/bin/adapter/repositories/AxiosSlackRepository.js +124 -0
  22. package/bin/adapter/repositories/AxiosSlackRepository.js.map +1 -0
  23. package/bin/adapter/repositories/BaseGitHubRepository.js +136 -0
  24. package/bin/adapter/repositories/BaseGitHubRepository.js.map +1 -0
  25. package/bin/adapter/repositories/GoogleSpreadsheetRepository.js +123 -0
  26. package/bin/adapter/repositories/GoogleSpreadsheetRepository.js.map +1 -0
  27. package/bin/adapter/repositories/GraphqlProjectRepository.js +167 -0
  28. package/bin/adapter/repositories/GraphqlProjectRepository.js.map +1 -0
  29. package/bin/adapter/repositories/LocalStorageCacheRepository.js +46 -0
  30. package/bin/adapter/repositories/LocalStorageCacheRepository.js.map +1 -0
  31. package/bin/adapter/repositories/LocalStorageRepository.js +30 -0
  32. package/bin/adapter/repositories/LocalStorageRepository.js.map +1 -0
  33. package/bin/adapter/repositories/SystemDateRepository.js +23 -0
  34. package/bin/adapter/repositories/SystemDateRepository.js.map +1 -0
  35. package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js +148 -0
  36. package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js.map +1 -0
  37. package/bin/adapter/repositories/issue/ApiV3IssueRepository.js +48 -0
  38. package/bin/adapter/repositories/issue/ApiV3IssueRepository.js.map +1 -0
  39. package/bin/adapter/repositories/issue/CheerioIssueRepository.js +120 -0
  40. package/bin/adapter/repositories/issue/CheerioIssueRepository.js.map +1 -0
  41. package/bin/adapter/repositories/issue/GraphqlProjectItemRepository.js +485 -0
  42. package/bin/adapter/repositories/issue/GraphqlProjectItemRepository.js.map +1 -0
  43. package/bin/adapter/repositories/issue/InternalGraphqlIssueRepository.js +114 -0
  44. package/bin/adapter/repositories/issue/InternalGraphqlIssueRepository.js.map +1 -0
  45. package/bin/adapter/repositories/issue/RestIssueRepository.js +79 -0
  46. package/bin/adapter/repositories/issue/RestIssueRepository.js.map +1 -0
  47. package/bin/adapter/repositories/issue/issueTimelineUtils.js +38 -0
  48. package/bin/adapter/repositories/issue/issueTimelineUtils.js.map +1 -0
  49. package/bin/adapter/repositories/utils.js +45 -0
  50. package/bin/adapter/repositories/utils.js.map +1 -0
  51. package/bin/domain/entities/Issue.js +3 -0
  52. package/bin/domain/entities/Issue.js.map +1 -0
  53. package/bin/domain/entities/Member.js +3 -0
  54. package/bin/domain/entities/Member.js.map +1 -0
  55. package/bin/domain/entities/Project.js +3 -0
  56. package/bin/domain/entities/Project.js.map +1 -0
  57. package/bin/domain/entities/ProjectField.js +3 -0
  58. package/bin/domain/entities/ProjectField.js.map +1 -0
  59. package/bin/domain/entities/ProjectFieldSingleSelect.js +3 -0
  60. package/bin/domain/entities/ProjectFieldSingleSelect.js.map +1 -0
  61. package/bin/domain/entities/ProjectFieldSingleSelectOption.js +3 -0
  62. package/bin/domain/entities/ProjectFieldSingleSelectOption.js.map +1 -0
  63. package/bin/domain/entities/WorkingTime.js +3 -0
  64. package/bin/domain/entities/WorkingTime.js.map +1 -0
  65. package/bin/domain/usecases/ActionAnnouncementUseCase.js +46 -0
  66. package/bin/domain/usecases/ActionAnnouncementUseCase.js.map +1 -0
  67. package/bin/domain/usecases/AnalyzeProblemByIssueUseCase.js +116 -0
  68. package/bin/domain/usecases/AnalyzeProblemByIssueUseCase.js.map +1 -0
  69. package/bin/domain/usecases/ClearNextActionHourUseCase.js +38 -0
  70. package/bin/domain/usecases/ClearNextActionHourUseCase.js.map +1 -0
  71. package/bin/domain/usecases/GenerateWorkingTimeReportUseCase.js +180 -0
  72. package/bin/domain/usecases/GenerateWorkingTimeReportUseCase.js.map +1 -0
  73. package/bin/domain/usecases/HandleScheduledEventUseCase.js +122 -0
  74. package/bin/domain/usecases/HandleScheduledEventUseCase.js.map +1 -0
  75. package/bin/domain/usecases/SetWorkflowManagementIssueToStoryUseCase.js +35 -0
  76. package/bin/domain/usecases/SetWorkflowManagementIssueToStoryUseCase.js.map +1 -0
  77. package/bin/domain/usecases/adapter-interfaces/DateRepository.js +3 -0
  78. package/bin/domain/usecases/adapter-interfaces/DateRepository.js.map +1 -0
  79. package/bin/domain/usecases/adapter-interfaces/IssueRepository.js +3 -0
  80. package/bin/domain/usecases/adapter-interfaces/IssueRepository.js.map +1 -0
  81. package/bin/domain/usecases/adapter-interfaces/ProjectRepository.js +3 -0
  82. package/bin/domain/usecases/adapter-interfaces/ProjectRepository.js.map +1 -0
  83. package/bin/domain/usecases/adapter-interfaces/SlackRepository.js +3 -0
  84. package/bin/domain/usecases/adapter-interfaces/SlackRepository.js.map +1 -0
  85. package/bin/domain/usecases/adapter-interfaces/SpreadsheetRepository.js +3 -0
  86. package/bin/domain/usecases/adapter-interfaces/SpreadsheetRepository.js.map +1 -0
  87. package/bin/index.js +13 -0
  88. package/bin/index.js.map +1 -0
  89. package/commitlint.config.js +6 -0
  90. package/jest.config.js +19 -0
  91. package/package.json +80 -0
  92. package/renovate.json +37 -0
  93. package/src/adapter/entry-points/cli/index.test.ts +20 -0
  94. package/src/adapter/entry-points/cli/index.ts +36 -0
  95. package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +95 -0
  96. package/src/adapter/repositories/AxiosSlackRepository.test.ts +119 -0
  97. package/src/adapter/repositories/AxiosSlackRepository.ts +184 -0
  98. package/src/adapter/repositories/BaseGitHubRepository.test.ts +95 -0
  99. package/src/adapter/repositories/BaseGitHubRepository.ts +172 -0
  100. package/src/adapter/repositories/GoogleSpreadsheetRepository.test.ts +124 -0
  101. package/src/adapter/repositories/GoogleSpreadsheetRepository.ts +151 -0
  102. package/src/adapter/repositories/GraphqlProjectRepository.test.ts +46 -0
  103. package/src/adapter/repositories/GraphqlProjectRepository.ts +236 -0
  104. package/src/adapter/repositories/LocalStorageCacheRepository.test.ts +146 -0
  105. package/src/adapter/repositories/LocalStorageCacheRepository.ts +53 -0
  106. package/src/adapter/repositories/LocalStorageRepository.integration.test.ts +142 -0
  107. package/src/adapter/repositories/LocalStorageRepository.test.ts +161 -0
  108. package/src/adapter/repositories/LocalStorageRepository.ts +21 -0
  109. package/src/adapter/repositories/SystemDateRepository.ts +20 -0
  110. package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.test.ts +158 -0
  111. package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts +274 -0
  112. package/src/adapter/repositories/issue/ApiV3IssueRepository.test.ts +26 -0
  113. package/src/adapter/repositories/issue/ApiV3IssueRepository.ts +59 -0
  114. package/src/adapter/repositories/issue/CheerioIssueRepository.test.ts +6610 -0
  115. package/src/adapter/repositories/issue/CheerioIssueRepository.ts +127 -0
  116. package/src/adapter/repositories/issue/GraphqlProjectItemRepository.test.ts +49 -0
  117. package/src/adapter/repositories/issue/GraphqlProjectItemRepository.ts +745 -0
  118. package/src/adapter/repositories/issue/InternalGraphqlIssueRepository.test.ts +71 -0
  119. package/src/adapter/repositories/issue/InternalGraphqlIssueRepository.ts +263 -0
  120. package/src/adapter/repositories/issue/RestIssueRepository.test.ts +29 -0
  121. package/src/adapter/repositories/issue/RestIssueRepository.ts +105 -0
  122. package/src/adapter/repositories/issue/issueTimelineUtils.test.ts +79 -0
  123. package/src/adapter/repositories/issue/issueTimelineUtils.ts +52 -0
  124. package/src/adapter/repositories/utils.test.ts +40 -0
  125. package/src/adapter/repositories/utils.ts +50 -0
  126. package/src/domain/entities/Issue.ts +23 -0
  127. package/src/domain/entities/Member.ts +3 -0
  128. package/src/domain/entities/Project.ts +29 -0
  129. package/src/domain/entities/ProjectField.ts +3 -0
  130. package/src/domain/entities/ProjectFieldSingleSelect.ts +8 -0
  131. package/src/domain/entities/ProjectFieldSingleSelectOption.ts +8 -0
  132. package/src/domain/entities/WorkingTime.ts +8 -0
  133. package/src/domain/usecases/ActionAnnouncementUseCase.ts +76 -0
  134. package/src/domain/usecases/AnalyzeProblemByIssueUseCase.ts +209 -0
  135. package/src/domain/usecases/ClearNextActionHourUseCase.ts +51 -0
  136. package/src/domain/usecases/GenerateWorkingTimeReportUseCase.test.ts +382 -0
  137. package/src/domain/usecases/GenerateWorkingTimeReportUseCase.ts +284 -0
  138. package/src/domain/usecases/HandleScheduledEventUseCase.test.ts +58 -0
  139. package/src/domain/usecases/HandleScheduledEventUseCase.ts +209 -0
  140. package/src/domain/usecases/SetWorkflowManagementIssueToStoryUseCase.ts +46 -0
  141. package/src/domain/usecases/adapter-interfaces/DateRepository.ts +5 -0
  142. package/src/domain/usecases/adapter-interfaces/IssueRepository.ts +44 -0
  143. package/src/domain/usecases/adapter-interfaces/ProjectRepository.ts +6 -0
  144. package/src/domain/usecases/adapter-interfaces/SlackRepository.ts +20 -0
  145. package/src/domain/usecases/adapter-interfaces/SpreadsheetRepository.ts +18 -0
  146. package/src/index.test.ts +8 -0
  147. package/src/index.ts +7 -0
  148. package/tsconfig.build.json +11 -0
  149. package/tsconfig.json +22 -0
  150. package/types/adapter/entry-points/cli/index.d.ts +3 -0
  151. package/types/adapter/entry-points/cli/index.d.ts.map +1 -0
  152. package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts +11 -0
  153. package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts.map +1 -0
  154. package/types/adapter/repositories/AxiosSlackRepository.d.ts +13 -0
  155. package/types/adapter/repositories/AxiosSlackRepository.d.ts.map +1 -0
  156. package/types/adapter/repositories/BaseGitHubRepository.d.ts +32 -0
  157. package/types/adapter/repositories/BaseGitHubRepository.d.ts.map +1 -0
  158. package/types/adapter/repositories/GoogleSpreadsheetRepository.d.ts +13 -0
  159. package/types/adapter/repositories/GoogleSpreadsheetRepository.d.ts.map +1 -0
  160. package/types/adapter/repositories/GraphqlProjectRepository.d.ts +13 -0
  161. package/types/adapter/repositories/GraphqlProjectRepository.d.ts.map +1 -0
  162. package/types/adapter/repositories/LocalStorageCacheRepository.d.ts +12 -0
  163. package/types/adapter/repositories/LocalStorageCacheRepository.d.ts.map +1 -0
  164. package/types/adapter/repositories/LocalStorageRepository.d.ts +7 -0
  165. package/types/adapter/repositories/LocalStorageRepository.d.ts.map +1 -0
  166. package/types/adapter/repositories/SystemDateRepository.d.ts +7 -0
  167. package/types/adapter/repositories/SystemDateRepository.d.ts.map +1 -0
  168. package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts +38 -0
  169. package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts.map +1 -0
  170. package/types/adapter/repositories/issue/ApiV3IssueRepository.d.ts +17 -0
  171. package/types/adapter/repositories/issue/ApiV3IssueRepository.d.ts.map +1 -0
  172. package/types/adapter/repositories/issue/CheerioIssueRepository.d.ts +31 -0
  173. package/types/adapter/repositories/issue/CheerioIssueRepository.d.ts.map +1 -0
  174. package/types/adapter/repositories/issue/GraphqlProjectItemRepository.d.ts +39 -0
  175. package/types/adapter/repositories/issue/GraphqlProjectItemRepository.d.ts.map +1 -0
  176. package/types/adapter/repositories/issue/InternalGraphqlIssueRepository.d.ts +83 -0
  177. package/types/adapter/repositories/issue/InternalGraphqlIssueRepository.d.ts.map +1 -0
  178. package/types/adapter/repositories/issue/RestIssueRepository.d.ts +16 -0
  179. package/types/adapter/repositories/issue/RestIssueRepository.d.ts.map +1 -0
  180. package/types/adapter/repositories/issue/issueTimelineUtils.d.ts +12 -0
  181. package/types/adapter/repositories/issue/issueTimelineUtils.d.ts.map +1 -0
  182. package/types/adapter/repositories/utils.d.ts +4 -0
  183. package/types/adapter/repositories/utils.d.ts.map +1 -0
  184. package/types/domain/entities/Issue.d.ts +24 -0
  185. package/types/domain/entities/Issue.d.ts.map +1 -0
  186. package/types/domain/entities/Member.d.ts +4 -0
  187. package/types/domain/entities/Member.d.ts.map +1 -0
  188. package/types/domain/entities/Project.d.ts +29 -0
  189. package/types/domain/entities/Project.d.ts.map +1 -0
  190. package/types/domain/entities/ProjectField.d.ts +3 -0
  191. package/types/domain/entities/ProjectField.d.ts.map +1 -0
  192. package/types/domain/entities/ProjectFieldSingleSelect.d.ts +8 -0
  193. package/types/domain/entities/ProjectFieldSingleSelect.d.ts.map +1 -0
  194. package/types/domain/entities/ProjectFieldSingleSelectOption.d.ts +8 -0
  195. package/types/domain/entities/ProjectFieldSingleSelectOption.d.ts.map +1 -0
  196. package/types/domain/entities/WorkingTime.d.ts +8 -0
  197. package/types/domain/entities/WorkingTime.d.ts.map +1 -0
  198. package/types/domain/usecases/ActionAnnouncementUseCase.d.ts +17 -0
  199. package/types/domain/usecases/ActionAnnouncementUseCase.d.ts.map +1 -0
  200. package/types/domain/usecases/AnalyzeProblemByIssueUseCase.d.ts +26 -0
  201. package/types/domain/usecases/AnalyzeProblemByIssueUseCase.d.ts.map +1 -0
  202. package/types/domain/usecases/ClearNextActionHourUseCase.d.ts +14 -0
  203. package/types/domain/usecases/ClearNextActionHourUseCase.d.ts.map +1 -0
  204. package/types/domain/usecases/GenerateWorkingTimeReportUseCase.d.ts +50 -0
  205. package/types/domain/usecases/GenerateWorkingTimeReportUseCase.d.ts.map +1 -0
  206. package/types/domain/usecases/HandleScheduledEventUseCase.d.ts +63 -0
  207. package/types/domain/usecases/HandleScheduledEventUseCase.d.ts.map +1 -0
  208. package/types/domain/usecases/SetWorkflowManagementIssueToStoryUseCase.d.ts +14 -0
  209. package/types/domain/usecases/SetWorkflowManagementIssueToStoryUseCase.d.ts.map +1 -0
  210. package/types/domain/usecases/adapter-interfaces/DateRepository.d.ts +6 -0
  211. package/types/domain/usecases/adapter-interfaces/DateRepository.d.ts.map +1 -0
  212. package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts +23 -0
  213. package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts.map +1 -0
  214. package/types/domain/usecases/adapter-interfaces/ProjectRepository.d.ts +6 -0
  215. package/types/domain/usecases/adapter-interfaces/ProjectRepository.d.ts.map +1 -0
  216. package/types/domain/usecases/adapter-interfaces/SlackRepository.d.ts +9 -0
  217. package/types/domain/usecases/adapter-interfaces/SlackRepository.d.ts.map +1 -0
  218. package/types/domain/usecases/adapter-interfaces/SpreadsheetRepository.d.ts +6 -0
  219. package/types/domain/usecases/adapter-interfaces/SpreadsheetRepository.d.ts.map +1 -0
  220. package/types/index.d.ts +10 -0
  221. package/types/index.d.ts.map +1 -0
@@ -0,0 +1,745 @@
1
+ import axios from 'axios';
2
+ import { BaseGitHubRepository } from '../BaseGitHubRepository';
3
+ export type ProjectItem = {
4
+ id: string;
5
+ nameWithOwner: string;
6
+ number: number;
7
+ title: string;
8
+ state: 'OPEN' | 'CLOSED' | 'MERGED';
9
+ url: string;
10
+ body: string;
11
+ customFields: {
12
+ name: string;
13
+ value: string | null;
14
+ }[];
15
+ };
16
+ export class GraphqlProjectItemRepository extends BaseGitHubRepository {
17
+ fetchItemId = async (
18
+ projectId: string,
19
+ owner: string,
20
+ repositoryName: string,
21
+ issueNumber: number,
22
+ ): Promise<string | undefined> => {
23
+ const graphqlQuery = {
24
+ query: `query GetProjectItemID( $owner: String!, $name: String!, $issueNumber: Int!) {
25
+ repository(owner: $owner, name: $name) {
26
+ issue(number: $issueNumber) {
27
+ projectItems(first: 2) {
28
+ nodes {
29
+ id
30
+ project {
31
+ id
32
+ }
33
+ }
34
+ }
35
+ }
36
+ }
37
+ }`,
38
+ variables: {
39
+ projectID: projectId,
40
+ owner: owner,
41
+ name: repositoryName,
42
+ issueNumber: issueNumber,
43
+ },
44
+ };
45
+
46
+ try {
47
+ const response = await axios<{
48
+ data: {
49
+ repository: {
50
+ issue: {
51
+ projectItems: {
52
+ nodes: {
53
+ id: string;
54
+ project: { id: string };
55
+ }[];
56
+ };
57
+ };
58
+ };
59
+ };
60
+ }>({
61
+ url: 'https://api.github.com/graphql',
62
+ method: 'post',
63
+ headers: {
64
+ Authorization: `Bearer ${this.ghToken}`,
65
+ 'Content-Type': 'application/json',
66
+ },
67
+ data: JSON.stringify(graphqlQuery),
68
+ });
69
+
70
+ const projectItems: {
71
+ id: string;
72
+ project: { id: string };
73
+ }[] = response.data.data.repository.issue.projectItems.nodes;
74
+ const projectItemId = projectItems.find(
75
+ (item) => item.project.id === projectId,
76
+ )?.id;
77
+ return projectItemId;
78
+ } catch (error) {
79
+ console.error('Error fetching GitHub Project V2 ID:', error);
80
+ return undefined;
81
+ }
82
+ };
83
+ fetchProjectItems = async (projectId: string): Promise<ProjectItem[]> => {
84
+ const graphqlQueryString = `
85
+ query GetProjectItems($projectId: ID!, $after: String) {
86
+ node(id: $projectId) {
87
+ ... on ProjectV2 {
88
+ items(first: 100, after: $after) {
89
+ totalCount
90
+ pageInfo {
91
+ endCursor
92
+ hasNextPage
93
+ }
94
+ nodes {
95
+ id
96
+ fieldValues(first: 10) {
97
+ nodes {
98
+ ... on ProjectV2ItemFieldTextValue {
99
+ text
100
+ field {
101
+ ... on ProjectV2Field{
102
+ name
103
+ }
104
+ }
105
+ }
106
+ ... on ProjectV2ItemFieldNumberValue {
107
+ number
108
+ id
109
+ field {
110
+ ... on ProjectV2Field{
111
+ name
112
+ }
113
+ }
114
+ }
115
+ ... on ProjectV2ItemFieldDateValue {
116
+ date
117
+ field {
118
+ ... on ProjectV2Field{
119
+ name
120
+ }
121
+ }
122
+ }
123
+ ... on ProjectV2ItemFieldSingleSelectValue {
124
+ name
125
+ field {
126
+ ... on ProjectV2SingleSelectField {
127
+ name
128
+ }
129
+ }
130
+ }
131
+ ... on ProjectV2ItemFieldIterationValue {
132
+ title
133
+ field {
134
+ ... on ProjectV2Field{
135
+ name
136
+ }
137
+ }
138
+ }
139
+ }
140
+ }
141
+ content {
142
+ ... on Issue {
143
+ number
144
+ title
145
+ state
146
+ url
147
+ body
148
+ repository {
149
+ nameWithOwner
150
+ }
151
+ }
152
+ ... on PullRequest {
153
+ number
154
+ title
155
+ state
156
+ url
157
+ body
158
+ repository {
159
+ nameWithOwner
160
+ }
161
+ }
162
+ }
163
+ }
164
+ }
165
+ }
166
+ }
167
+ }
168
+ `;
169
+ const callGraphql = async (
170
+ projectId: string,
171
+ after: string | null,
172
+ ): Promise<{
173
+ node: {
174
+ items: {
175
+ totalCount: number;
176
+ pageInfo: {
177
+ endCursor: string;
178
+ startCursor: string;
179
+ };
180
+ nodes: {
181
+ id: string;
182
+ fieldValues: {
183
+ nodes: {
184
+ text: string;
185
+ number: number;
186
+ date: string;
187
+ field: {
188
+ name: string;
189
+ };
190
+ }[];
191
+ };
192
+ content: {
193
+ repository: { nameWithOwner: string };
194
+ number: number;
195
+ title: string;
196
+ state: string;
197
+ url: string;
198
+ body: string;
199
+ };
200
+ }[];
201
+ };
202
+ };
203
+ }> => {
204
+ const graphqlQuery = {
205
+ query: graphqlQueryString,
206
+ variables: {
207
+ projectId: projectId,
208
+ after: after,
209
+ },
210
+ };
211
+ const response = await axios<{
212
+ data: {
213
+ node: {
214
+ items: {
215
+ totalCount: number;
216
+ pageInfo: {
217
+ endCursor: string;
218
+ startCursor: string;
219
+ };
220
+ nodes: {
221
+ id: string;
222
+ fieldValues: {
223
+ nodes: {
224
+ text: string;
225
+ number: number;
226
+ date: string;
227
+ field: {
228
+ name: string;
229
+ };
230
+ }[];
231
+ };
232
+ content: {
233
+ repository: { nameWithOwner: string };
234
+ number: number;
235
+ title: string;
236
+ state: string;
237
+ url: string;
238
+ body: string;
239
+ };
240
+ }[];
241
+ };
242
+ };
243
+ };
244
+ }>({
245
+ url: 'https://api.github.com/graphql',
246
+ method: 'post',
247
+ headers: {
248
+ Authorization: `Bearer ${this.ghToken}`,
249
+ 'Content-Type': 'application/json',
250
+ },
251
+ data: JSON.stringify(graphqlQuery),
252
+ });
253
+ if (!response.data.data) {
254
+ throw new Error('No data returned from GitHub API');
255
+ }
256
+ return response.data.data;
257
+ };
258
+ const issues: {
259
+ id: string;
260
+ nameWithOwner: string;
261
+ number: number;
262
+ title: string;
263
+ state: 'OPEN' | 'CLOSED' | 'MERGED';
264
+ url: string;
265
+ body: string;
266
+ customFields: {
267
+ name: string;
268
+ value: string | null;
269
+ }[];
270
+ }[] = [];
271
+ let after: string | null = null;
272
+ let totalCount = 1;
273
+
274
+ while (issues.length < totalCount) {
275
+ const data = await callGraphql(projectId, after);
276
+ const projectItems: {
277
+ id: string;
278
+ fieldValues: {
279
+ nodes: {
280
+ text?: string;
281
+ number?: number;
282
+ date?: string;
283
+ name?: string;
284
+ field: {
285
+ name: string;
286
+ };
287
+ }[];
288
+ };
289
+ content: {
290
+ repository: { nameWithOwner: string };
291
+ number: number;
292
+ title: string;
293
+ state: string;
294
+ url: string;
295
+ body: string;
296
+ };
297
+ }[] = data.node.items.nodes;
298
+ projectItems
299
+ // .filter(item => item.content.repository !== undefined)
300
+ .forEach((item) => {
301
+ if (!item || !item.content || !item.content.repository) {
302
+ return;
303
+ }
304
+ issues.push({
305
+ id: item.id,
306
+ nameWithOwner: item.content.repository.nameWithOwner,
307
+ number: item.content.number,
308
+ title: item.content.title,
309
+ state: this.convertStrToState(item.content.state),
310
+ url: item.content.url,
311
+ body: item.content.body,
312
+ customFields: item.fieldValues.nodes
313
+ .filter((field) => !!field.field)
314
+ .map((field) => {
315
+ return {
316
+ name: field.field.name,
317
+ value:
318
+ field.name ??
319
+ field.text ??
320
+ field.number?.toString() ??
321
+ field.date ??
322
+ null,
323
+ };
324
+ }),
325
+ });
326
+ });
327
+ totalCount = data.node.items.totalCount;
328
+ const pageInfo = data.node.items.pageInfo;
329
+ after = pageInfo.endCursor;
330
+ }
331
+ return issues;
332
+ };
333
+ getProjectItemFieldsFromIssueUrl = async (
334
+ issueUrl: string,
335
+ ): Promise<
336
+ {
337
+ fieldName: string;
338
+ fieldValue: string;
339
+ }[]
340
+ > => {
341
+ const { owner, repo, issueNumber } = this.extractIssueFromUrl(issueUrl);
342
+ return this.getProjectItemFields(owner, repo, issueNumber);
343
+ };
344
+
345
+ getProjectItemFields = async (
346
+ owner: string,
347
+ repositoryName: string,
348
+ issueNumber: number,
349
+ ): Promise<
350
+ {
351
+ fieldName: string;
352
+ fieldValue: string;
353
+ }[]
354
+ > => {
355
+ const graphqlQueryString = `
356
+ query GetProjectFields($owner: String!, $repository: String!, $issueNumber: Int!){
357
+ repository(owner: $owner, name: $repository) {
358
+ issue(number: $issueNumber) {
359
+ projectItems(first: 2) {
360
+ nodes {
361
+ id
362
+ fieldValues(first: 10) {
363
+ totalCount
364
+ nodes {
365
+ __typename
366
+ ... on ProjectV2ItemFieldDateValue {
367
+ date
368
+ field {
369
+ ... on ProjectV2Field {
370
+ name
371
+ }
372
+ }
373
+ }
374
+ ... on ProjectV2ItemFieldSingleSelectValue {
375
+ name
376
+ field {
377
+ ... on ProjectV2SingleSelectField {
378
+ name
379
+ }
380
+ }
381
+ }
382
+ ... on ProjectV2ItemFieldUserValue {
383
+ users {
384
+ totalCount
385
+ nodes {
386
+ login
387
+ }
388
+ }
389
+ field {
390
+ ... on ProjectV2Field {
391
+ name
392
+ }
393
+ }
394
+ }
395
+ ... on ProjectV2ItemFieldRepositoryValue {
396
+ repository {
397
+ name
398
+ }
399
+ field {
400
+ __typename
401
+ ... on ProjectV2Field {
402
+ name
403
+ }
404
+ }
405
+ }
406
+ ... on ProjectV2ItemFieldTextValue {
407
+ text
408
+ }
409
+ }
410
+ }
411
+ }
412
+ }
413
+ }
414
+ }
415
+ }
416
+
417
+ `;
418
+ const graphqlQuery = {
419
+ query: graphqlQueryString,
420
+ variables: {
421
+ owner: owner,
422
+ repository: repositoryName,
423
+ issueNumber: issueNumber,
424
+ },
425
+ };
426
+ const response = await axios<{
427
+ data: {
428
+ repository: {
429
+ issue: {
430
+ projectItems: {
431
+ nodes: {
432
+ fieldValues: {
433
+ nodes: {
434
+ __typename: string;
435
+ field: { name: string };
436
+ date: string;
437
+ name: string;
438
+ text: string;
439
+ repository: { name: string };
440
+ user: { login: string };
441
+ }[];
442
+ };
443
+ }[];
444
+ };
445
+ };
446
+ };
447
+ };
448
+ }>({
449
+ url: 'https://api.github.com/graphql',
450
+ method: 'post',
451
+ headers: {
452
+ Authorization: `Bearer ${this.ghToken}`,
453
+ 'Content-Type': 'application/json',
454
+ },
455
+ data: JSON.stringify(graphqlQuery),
456
+ });
457
+
458
+ const data = response.data.data;
459
+ const issueFields: {
460
+ fieldName: string;
461
+ fieldValue: string;
462
+ }[] = [];
463
+ if (!data.repository.issue) {
464
+ return issueFields;
465
+ }
466
+ const projectItems: {
467
+ __typename: string;
468
+ field: {
469
+ name: string;
470
+ };
471
+ user?: {
472
+ login: string;
473
+ };
474
+ repository?: {
475
+ name: string;
476
+ };
477
+ date?: string;
478
+ name?: string;
479
+ text?: string;
480
+ }[] = data.repository.issue.projectItems.nodes[0].fieldValues.nodes;
481
+ projectItems.forEach((item) => {
482
+ issueFields.push({
483
+ fieldName: item.field?.name ?? '',
484
+ fieldValue:
485
+ item.date ??
486
+ item.name ??
487
+ item.text ??
488
+ item.repository?.name ??
489
+ item.user?.login ??
490
+ '',
491
+ });
492
+ });
493
+ return issueFields;
494
+ };
495
+ fetchProjectItemByUrl = async (
496
+ issueUrl: string,
497
+ ): Promise<ProjectItem | null> => {
498
+ const { owner, repo, issueNumber } = this.extractIssueFromUrl(issueUrl);
499
+ const graphql = `query GetIssue($owner: String!, $repo: String!, $number: Int!) {
500
+ repository(owner: $owner, name: $repo) {
501
+ issue(number: $number) {
502
+ number
503
+ title
504
+ state
505
+ url
506
+ body
507
+ repository {
508
+ nameWithOwner
509
+ }
510
+ projectItems(first: 10) {
511
+ nodes {
512
+ fieldValues(first: 10) {
513
+ nodes {
514
+ ... on ProjectV2ItemFieldTextValue {
515
+ text
516
+ field {
517
+ ... on ProjectV2Field {
518
+ name
519
+ }
520
+ }
521
+ }
522
+ ... on ProjectV2ItemFieldNumberValue {
523
+ number
524
+ id
525
+ field {
526
+ ... on ProjectV2Field {
527
+ name
528
+ }
529
+ }
530
+ }
531
+ ... on ProjectV2ItemFieldDateValue {
532
+ date
533
+ field {
534
+ ... on ProjectV2Field {
535
+ name
536
+ }
537
+ }
538
+ }
539
+ ... on ProjectV2ItemFieldSingleSelectValue {
540
+ name
541
+ field {
542
+ ... on ProjectV2SingleSelectField {
543
+ name
544
+ }
545
+ }
546
+ }
547
+ ... on ProjectV2ItemFieldIterationValue {
548
+ title
549
+ field {
550
+ ... on ProjectV2Field {
551
+ name
552
+ }
553
+ }
554
+ }
555
+ }
556
+ }
557
+ }
558
+ }
559
+ }
560
+ }
561
+ }`;
562
+ const graphqlQuery = {
563
+ query: graphql,
564
+ variables: {
565
+ owner: owner,
566
+ repo: repo,
567
+ number: issueNumber,
568
+ },
569
+ };
570
+ const response = await axios<{
571
+ data: {
572
+ repository: {
573
+ issue: {
574
+ number: number;
575
+ title: string;
576
+ state: string;
577
+ url: string;
578
+ body: string;
579
+ repository: { nameWithOwner: string };
580
+ projectItems: {
581
+ nodes: {
582
+ id: string;
583
+ fieldValues: {
584
+ nodes: {
585
+ text: string;
586
+ number: number;
587
+ date: string;
588
+ name: string;
589
+ field: {
590
+ name: string;
591
+ };
592
+ }[];
593
+ };
594
+ }[];
595
+ };
596
+ };
597
+ };
598
+ };
599
+ }>({
600
+ url: 'https://api.github.com/graphql',
601
+ method: 'post',
602
+ headers: {
603
+ Authorization: `Bearer ${this.ghToken}`,
604
+ 'Content-Type': 'application/json',
605
+ },
606
+ data: JSON.stringify(graphqlQuery),
607
+ });
608
+ const data = response.data.data;
609
+ if (!data.repository.issue) {
610
+ return null;
611
+ }
612
+ const projectItems: {
613
+ id: string;
614
+ fieldValues: {
615
+ nodes: {
616
+ text: string;
617
+ number: number;
618
+ date: string;
619
+ name: string;
620
+ field: {
621
+ name: string;
622
+ };
623
+ }[];
624
+ };
625
+ }[] = data.repository.issue.projectItems.nodes;
626
+ const item = projectItems[0];
627
+ return {
628
+ id: item.id,
629
+ nameWithOwner: data.repository.issue.repository.nameWithOwner,
630
+ number: data.repository.issue.number,
631
+ title: data.repository.issue.title,
632
+ state: this.convertStrToState(data.repository.issue.state),
633
+ url: data.repository.issue.url,
634
+ body: data.repository.issue.body,
635
+ customFields: item.fieldValues.nodes
636
+ .filter((field) => !!field.field)
637
+ .map((field) => {
638
+ return {
639
+ name: field.field.name,
640
+ value:
641
+ field.name ??
642
+ field.text ??
643
+ field.number?.toString() ??
644
+ field.date ??
645
+ null,
646
+ };
647
+ }),
648
+ };
649
+ };
650
+ convertStrToState = (state: string): 'OPEN' | 'CLOSED' | 'MERGED' => {
651
+ return state === 'MERGED'
652
+ ? 'MERGED'
653
+ : state === 'CLOSED'
654
+ ? 'CLOSED'
655
+ : state === 'OPEN'
656
+ ? 'OPEN'
657
+ : 'OPEN';
658
+ };
659
+
660
+ updateProjectField = async (
661
+ projectId: string,
662
+ fieldId: string,
663
+ itemId: string,
664
+ value:
665
+ | { text: string }
666
+ | { number: number }
667
+ | { date: string }
668
+ | { singleSelectOptionId: string },
669
+ ): Promise<void> => {
670
+ const graphqlQuery = {
671
+ query: `mutation {
672
+ updateProjectV2ItemFieldValue(input: {
673
+ projectId: "${projectId}"
674
+ fieldId: "${fieldId}"
675
+ itemId: "${itemId}"
676
+ value: ${JSON.stringify(value).replace(/"([^"]+)":/g, '$1:')},
677
+ }) {
678
+ clientMutationId
679
+ }
680
+ }`,
681
+ };
682
+
683
+ const res = await axios<{
684
+ data: {
685
+ updateProjectV2ItemFieldValue: {
686
+ clientMutationId: string;
687
+ };
688
+ };
689
+ errors: { message: string }[];
690
+ }>({
691
+ url: 'https://api.github.com/graphql',
692
+ method: 'post',
693
+ headers: {
694
+ Authorization: `Bearer ${this.ghToken}`,
695
+ 'Content-Type': 'application/json',
696
+ },
697
+ data: JSON.stringify(graphqlQuery),
698
+ });
699
+ if (res.status !== 200) {
700
+ throw new Error('Failed to update project field');
701
+ } else if (res.data.errors) {
702
+ throw new Error(res.data.errors.map((e) => e.message).join('\n'));
703
+ }
704
+ };
705
+
706
+ clearProjectField = async (
707
+ projectId: string,
708
+ fieldId: string,
709
+ itemId: string,
710
+ ): Promise<void> => {
711
+ const graphqlQuery = {
712
+ query: `mutation {
713
+ clearProjectV2ItemFieldValue(input: {
714
+ projectId: "${projectId}"
715
+ fieldId: "${fieldId}"
716
+ itemId: "${itemId}"
717
+ }) {
718
+ clientMutationId
719
+ }
720
+ }`,
721
+ };
722
+
723
+ const res = await axios<{
724
+ data: {
725
+ clearProjectV2ItemFieldValue: {
726
+ clientMutationId: string;
727
+ };
728
+ };
729
+ errors: { message: string }[];
730
+ }>({
731
+ url: 'https://api.github.com/graphql',
732
+ method: 'post',
733
+ headers: {
734
+ Authorization: `Bearer ${this.ghToken}`,
735
+ 'Content-Type': 'application/json',
736
+ },
737
+ data: JSON.stringify(graphqlQuery),
738
+ });
739
+ if (res.status !== 200) {
740
+ throw new Error('Failed to clear project field');
741
+ } else if (res.data.errors) {
742
+ throw new Error(res.data.errors.map((e) => e.message).join('\n'));
743
+ }
744
+ };
745
+ }