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,274 @@
1
+ import { IssueRepository } from '../../../domain/usecases/adapter-interfaces/IssueRepository';
2
+ import { Project } from '../../../domain/entities/Project';
3
+ import { Issue } from '../../../domain/entities/Issue';
4
+ import { ApiV3IssueRepository } from './ApiV3IssueRepository';
5
+ import {
6
+ CheerioIssueRepository,
7
+ Issue as CheerioIssue,
8
+ } from './CheerioIssueRepository';
9
+ import { RestIssueRepository } from './RestIssueRepository';
10
+ import {
11
+ GraphqlProjectItemRepository,
12
+ ProjectItem,
13
+ } from './GraphqlProjectItemRepository';
14
+ import { WorkingTime } from '../../../domain/entities/WorkingTime';
15
+ import { LocalStorageCacheRepository } from '../LocalStorageCacheRepository';
16
+ import typia from 'typia';
17
+ import { BaseGitHubRepository } from '../BaseGitHubRepository';
18
+ import { normalizeFieldName } from '../utils';
19
+
20
+ export class ApiV3CheerioRestIssueRepository
21
+ extends BaseGitHubRepository
22
+ implements IssueRepository
23
+ {
24
+ constructor(
25
+ readonly apiV3IssueRepository: Pick<ApiV3IssueRepository, 'searchIssue'>,
26
+ readonly cheerioIssueRepository: Pick<CheerioIssueRepository, 'getIssue'>,
27
+ readonly restIssueRepository: Pick<
28
+ RestIssueRepository,
29
+ 'createNewIssue' | 'updateIssue'
30
+ >,
31
+ readonly graphqlProjectItemRepository: Pick<
32
+ GraphqlProjectItemRepository,
33
+ | 'fetchProjectItems'
34
+ | 'fetchProjectItemByUrl'
35
+ | 'updateProjectField'
36
+ | 'clearProjectField'
37
+ >,
38
+ readonly localStorageCacheRepository: Pick<
39
+ LocalStorageCacheRepository,
40
+ 'getLatest' | 'set'
41
+ >,
42
+ ) {
43
+ super();
44
+ }
45
+
46
+ convertProjectItemAndCheerioIssueToIssue = async (
47
+ item: ProjectItem,
48
+ cheerioIssue: CheerioIssue,
49
+ ): Promise<Issue> => {
50
+ const timeline: WorkingTime[] = cheerioIssue.inProgressTimeline;
51
+ const nextActionDate = item.customFields.find(
52
+ (field) => normalizeFieldName(field.name) === 'nextactiondate',
53
+ )?.value;
54
+ const nextActionHour = item.customFields.find(
55
+ (field) => normalizeFieldName(field.name) === 'nextactionhour',
56
+ )?.value;
57
+ const estimationMinutes = item.customFields.find(
58
+ (field) => normalizeFieldName(field.name) === 'estimationminutes',
59
+ )?.value;
60
+ const story = item.customFields.find(
61
+ (field) => normalizeFieldName(field.name) === 'story',
62
+ )?.value;
63
+ const status = item.customFields.find(
64
+ (field) => normalizeFieldName(field.name) === 'status',
65
+ )?.value;
66
+ const { owner, repo } = this.extractIssueFromUrl(item.url);
67
+
68
+ return {
69
+ nameWithOwner: item.nameWithOwner,
70
+ url: item.url,
71
+ title: item.title,
72
+ number: item.number,
73
+ state: item.state,
74
+ labels: cheerioIssue.labels,
75
+ assignees: cheerioIssue.assignees,
76
+ workingTimeline: timeline,
77
+ nextActionDate: nextActionDate ? new Date(nextActionDate) : null,
78
+ nextActionHour: nextActionHour ? parseInt(nextActionHour) : null,
79
+ estimationMinutes: estimationMinutes ? parseInt(estimationMinutes) : null,
80
+ status: status || null,
81
+ story: story || null,
82
+ org: owner,
83
+ repo: repo,
84
+ body: item.body,
85
+ itemId: item.id,
86
+ isPr: item.url.includes('/pull/'),
87
+ };
88
+ };
89
+ getAllIssuesFromCache = async (
90
+ cacheKey: string,
91
+ allowCacheMinutes: number,
92
+ ): Promise<Issue[] | null> => {
93
+ const cache = await this.localStorageCacheRepository.getLatest(cacheKey);
94
+ if (cache) {
95
+ const now = new Date();
96
+ const cacheTimestamp = cache.timestamp;
97
+ const diff = now.getTime() - cacheTimestamp.getTime();
98
+ if (diff < allowCacheMinutes * 60 * 1000) {
99
+ if (!Array.isArray(cache.value)) {
100
+ return null;
101
+ }
102
+ const issues = cache.value
103
+ .filter(
104
+ (issue: unknown): issue is object => typeof issue === 'object',
105
+ )
106
+ .map((issue: object): object => {
107
+ const nextActionDate =
108
+ !('nextActionDate' in issue) ||
109
+ typeof issue.nextActionDate !== 'string' ||
110
+ issue.nextActionDate === null
111
+ ? null
112
+ : new Date(issue.nextActionDate);
113
+ const workingTimeline =
114
+ !('workingTimeline' in issue) ||
115
+ !Array.isArray(issue.workingTimeline)
116
+ ? []
117
+ : issue.workingTimeline.map((event: object): object => {
118
+ const startedAt =
119
+ !('startedAt' in event) ||
120
+ typeof event.startedAt !== 'string' ||
121
+ event.startedAt === null
122
+ ? null
123
+ : new Date(event.startedAt);
124
+ const endedAt =
125
+ !('endedAt' in event) ||
126
+ typeof event.endedAt !== 'string' ||
127
+ event.endedAt === null
128
+ ? null
129
+ : new Date(event.endedAt);
130
+ return {
131
+ ...event,
132
+ startedAt,
133
+ endedAt,
134
+ };
135
+ });
136
+
137
+ return {
138
+ ...issue,
139
+ nextActionDate: nextActionDate,
140
+ workingTimeline: workingTimeline,
141
+ };
142
+ });
143
+
144
+ if (typia.is<Issue[]>(issues)) {
145
+ return issues;
146
+ }
147
+ }
148
+ }
149
+ return null;
150
+ };
151
+
152
+ getAllIssues = async (
153
+ projectId: Project['id'],
154
+ allowCacheMinutes: number,
155
+ ): Promise<{
156
+ issues: Issue[];
157
+ cacheUsed: boolean;
158
+ }> => {
159
+ const cacheKey = `allIssues-${projectId}`;
160
+ const cachedIssues = await this.getAllIssuesFromCache(
161
+ cacheKey,
162
+ allowCacheMinutes,
163
+ );
164
+ if (cachedIssues) {
165
+ return { issues: cachedIssues, cacheUsed: true };
166
+ }
167
+ const issues = await this.getAllIssuesFromGitHub(projectId);
168
+ await this.localStorageCacheRepository.set(cacheKey, issues);
169
+ return { issues, cacheUsed: false };
170
+ };
171
+ getAllIssuesFromGitHub = async (
172
+ projectId: Project['id'],
173
+ ): Promise<Issue[]> => {
174
+ const items =
175
+ await this.graphqlProjectItemRepository.fetchProjectItems(projectId);
176
+
177
+ const issues = await Promise.all(
178
+ items.map(async (item): Promise<Issue> => {
179
+ const cheerioIssue = await this.cheerioIssueRepository.getIssue(
180
+ item.url,
181
+ );
182
+ return this.convertProjectItemAndCheerioIssueToIssue(
183
+ item,
184
+ cheerioIssue,
185
+ );
186
+ }),
187
+ );
188
+ return issues;
189
+ };
190
+ createNewIssue = async (
191
+ org: string,
192
+ repo: string,
193
+ title: string,
194
+ body: string,
195
+ assignees: string[],
196
+ labels: string[],
197
+ ): Promise<void> => {
198
+ await this.restIssueRepository.createNewIssue(
199
+ org,
200
+ repo,
201
+ title,
202
+ body,
203
+ assignees,
204
+ labels,
205
+ );
206
+ };
207
+ updateIssue = async (issue: Issue): Promise<void> => {
208
+ await this.restIssueRepository.updateIssue(issue);
209
+ };
210
+ getIssueByUrl = async (url: string): Promise<Issue | null> => {
211
+ const projectItem =
212
+ await this.graphqlProjectItemRepository.fetchProjectItemByUrl(url);
213
+ if (!projectItem) {
214
+ return null;
215
+ }
216
+ const cheerioIssue = await this.cheerioIssueRepository.getIssue(url);
217
+ return this.convertProjectItemAndCheerioIssueToIssue(
218
+ projectItem,
219
+ cheerioIssue,
220
+ );
221
+ };
222
+ updateNextActionDate = async (
223
+ project: Project & { nextActionDate: Required<Project['nextActionDate']> },
224
+ issue: Issue,
225
+ date: Date,
226
+ ): Promise<void> => {
227
+ if (project.nextActionDate === null) {
228
+ throw new Error('nextActionDate is not defined');
229
+ }
230
+ return this.graphqlProjectItemRepository.updateProjectField(
231
+ project.id,
232
+ project.nextActionDate.fieldId,
233
+ issue.itemId,
234
+ { date: date.toISOString() },
235
+ );
236
+ };
237
+ updateNextActionHour = async (
238
+ project: Project & {
239
+ nextActionHour: NonNullable<Project['nextActionHour']>;
240
+ },
241
+ issue: Issue,
242
+ hour: number,
243
+ ): Promise<void> => {
244
+ return this.graphqlProjectItemRepository.updateProjectField(
245
+ project.id,
246
+ project.nextActionHour.fieldId,
247
+ issue.itemId,
248
+ { number: hour },
249
+ );
250
+ };
251
+ updateStory = async (
252
+ project: Project & { story: NonNullable<Project['story']> },
253
+ issue: Issue,
254
+ storyOptionId: string,
255
+ ): Promise<void> => {
256
+ await this.graphqlProjectItemRepository.updateProjectField(
257
+ project.id,
258
+ project.story.fieldId,
259
+ issue.itemId,
260
+ { singleSelectOptionId: storyOptionId },
261
+ );
262
+ };
263
+ clearProjectField = async (
264
+ project: Project,
265
+ fieldId: string,
266
+ issue: Issue,
267
+ ): Promise<void> => {
268
+ return this.graphqlProjectItemRepository.clearProjectField(
269
+ project.id,
270
+ fieldId,
271
+ issue.itemId,
272
+ );
273
+ };
274
+ }
@@ -0,0 +1,26 @@
1
+ import { ApiV3IssueRepository } from './ApiV3IssueRepository';
2
+
3
+ describe('ApiV3IssueRepository', () => {
4
+ const repository = new ApiV3IssueRepository(
5
+ '',
6
+ process.env.GH_TOKEN || 'dummy',
7
+ );
8
+ test('searchIssue', async () => {
9
+ const result = await repository.searchIssue({
10
+ owner: 'HiromiShikata',
11
+ repositoryName: 'test-repository',
12
+ type: 'issue',
13
+ state: 'open',
14
+ title: 'In progress',
15
+ createdFrom: '2024-04-21',
16
+ assignee: 'HiromiShikata',
17
+ });
18
+ expect(result).toEqual([
19
+ {
20
+ number: 38,
21
+ title: 'In progress test title',
22
+ url: 'https://github.com/HiromiShikata/test-repository/issues/38',
23
+ },
24
+ ]);
25
+ });
26
+ });
@@ -0,0 +1,59 @@
1
+ import axios from 'axios';
2
+ import { BaseGitHubRepository } from '../BaseGitHubRepository';
3
+
4
+ export class ApiV3IssueRepository extends BaseGitHubRepository {
5
+ searchIssue = async (query: {
6
+ owner: string;
7
+ repositoryName: string;
8
+ type?: 'issue' | 'pr';
9
+ state?: 'open' | 'closed' | 'all';
10
+ title?: string;
11
+ createdFrom?: string;
12
+ assignee?: string;
13
+ }): Promise<
14
+ {
15
+ url: string;
16
+ title: string;
17
+ number: string;
18
+ }[]
19
+ > => {
20
+ // example: curl -H "Authorization: token $GH_TOKEN" -H "Accept: application/vnd.github.v3+json" "https://api.github.com/search/issues?q=repo:$OWNER/$REPO+type:issue+state:open+in:title+'Maintain%20Kanban'&created=2024-05-08&assignee=HiromiShkata"
21
+ let url = `https://api.github.com/search/issues?q=repo:${query.owner}/${query.repositoryName}`;
22
+ if (query.type) {
23
+ url += `+type:${query.type}`;
24
+ }
25
+ if (query.state) {
26
+ url += `+state:${query.state}`;
27
+ }
28
+ if (query.title) {
29
+ url += `+in:title+'${query.title}'`;
30
+ }
31
+ if (query.createdFrom) {
32
+ url += `+created:>=${query.createdFrom}`;
33
+ }
34
+ if (query.assignee) {
35
+ url += `&assignee=${query.assignee}`;
36
+ }
37
+
38
+ const response = await axios.get<{
39
+ items: {
40
+ html_url: string;
41
+ title: string;
42
+ number: string;
43
+ }[];
44
+ }>(url, {
45
+ headers: {
46
+ Authorization: `token ${this.ghToken}`,
47
+ Accept: 'application/vnd.github.v3+json',
48
+ },
49
+ });
50
+ if (response.status !== 200) {
51
+ throw new Error(`Failed to search issue: ${response.status}`);
52
+ }
53
+ return response.data.items.map((item) => ({
54
+ url: item.html_url,
55
+ title: item.title,
56
+ number: item.number,
57
+ }));
58
+ };
59
+ }