github-issue-tower-defence-management 1.121.0 → 1.122.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.
- package/CHANGELOG.md +14 -0
- package/README.md +6 -2
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +7 -2
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -1
- package/bin/adapter/entry-points/handlers/inTmuxByHumanDataWriter.js +2 -1
- package/bin/adapter/entry-points/handlers/inTmuxByHumanDataWriter.js.map +1 -1
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js +14 -7
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js.map +1 -1
- package/bin/adapter/repositories/issue/GraphqlProjectItemRepository.js +297 -137
- package/bin/adapter/repositories/issue/GraphqlProjectItemRepository.js.map +1 -1
- package/bin/domain/usecases/intmux/GenerateInTmuxByHumanDataUseCase.js +2 -2
- package/bin/domain/usecases/intmux/GenerateInTmuxByHumanDataUseCase.js.map +1 -1
- package/package.json +1 -1
- package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +2 -0
- package/src/adapter/entry-points/handlers/inTmuxByHumanDataWriter.test.ts +11 -0
- package/src/adapter/entry-points/handlers/inTmuxByHumanDataWriter.ts +3 -0
- package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.test.ts +175 -7
- package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts +22 -8
- package/src/adapter/repositories/issue/GraphqlProjectItemRepository.test.ts +294 -0
- package/src/adapter/repositories/issue/GraphqlProjectItemRepository.ts +400 -165
- package/src/domain/usecases/intmux/GenerateInTmuxByHumanDataUseCase.test.ts +11 -0
- package/src/domain/usecases/intmux/GenerateInTmuxByHumanDataUseCase.ts +3 -1
- package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts.map +1 -1
- package/types/adapter/entry-points/handlers/inTmuxByHumanDataWriter.d.ts +1 -0
- package/types/adapter/entry-points/handlers/inTmuxByHumanDataWriter.d.ts.map +1 -1
- package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts +3 -2
- package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts.map +1 -1
- package/types/adapter/repositories/issue/GraphqlProjectItemRepository.d.ts +10 -0
- package/types/adapter/repositories/issue/GraphqlProjectItemRepository.d.ts.map +1 -1
- package/types/domain/usecases/intmux/GenerateInTmuxByHumanDataUseCase.d.ts +1 -0
- package/types/domain/usecases/intmux/GenerateInTmuxByHumanDataUseCase.d.ts.map +1 -1
|
@@ -21,8 +21,145 @@ export type ProjectItem = {
|
|
|
21
21
|
value: string | null;
|
|
22
22
|
}[];
|
|
23
23
|
};
|
|
24
|
+
export type ProjectItemLight = {
|
|
25
|
+
id: string;
|
|
26
|
+
updatedAt: string;
|
|
27
|
+
url: string;
|
|
28
|
+
number: number;
|
|
29
|
+
};
|
|
30
|
+
type ProjectV2ItemFieldValueNode = {
|
|
31
|
+
text?: string;
|
|
32
|
+
number?: number;
|
|
33
|
+
date?: string;
|
|
34
|
+
name?: string;
|
|
35
|
+
field: {
|
|
36
|
+
name: string;
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
type ProjectV2ItemContentNode = {
|
|
40
|
+
repository: { nameWithOwner: string };
|
|
41
|
+
number: number;
|
|
42
|
+
title: string;
|
|
43
|
+
state: string;
|
|
44
|
+
url: string;
|
|
45
|
+
createdAt: string;
|
|
46
|
+
updatedAt: string;
|
|
47
|
+
author: { login: string } | null;
|
|
48
|
+
labels: { nodes: { name: string }[] };
|
|
49
|
+
assignees: { nodes: { login: string }[] };
|
|
50
|
+
closingIssuesReferences?: { nodes: { url: string }[] };
|
|
51
|
+
};
|
|
52
|
+
type ProjectV2ItemNode = {
|
|
53
|
+
id: string;
|
|
54
|
+
fieldValues: {
|
|
55
|
+
nodes: ProjectV2ItemFieldValueNode[];
|
|
56
|
+
};
|
|
57
|
+
content: ProjectV2ItemContentNode;
|
|
58
|
+
};
|
|
59
|
+
const PROJECT_V2_ITEM_FIELD_VALUES_AND_CONTENT_SELECTION = `
|
|
60
|
+
fieldValues(first: 10) {
|
|
61
|
+
nodes {
|
|
62
|
+
... on ProjectV2ItemFieldTextValue {
|
|
63
|
+
text
|
|
64
|
+
field {
|
|
65
|
+
... on ProjectV2Field{
|
|
66
|
+
name
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
... on ProjectV2ItemFieldNumberValue {
|
|
71
|
+
number
|
|
72
|
+
id
|
|
73
|
+
field {
|
|
74
|
+
... on ProjectV2Field{
|
|
75
|
+
name
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
... on ProjectV2ItemFieldDateValue {
|
|
80
|
+
date
|
|
81
|
+
field {
|
|
82
|
+
... on ProjectV2Field{
|
|
83
|
+
name
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
... on ProjectV2ItemFieldSingleSelectValue {
|
|
88
|
+
name
|
|
89
|
+
field {
|
|
90
|
+
... on ProjectV2SingleSelectField {
|
|
91
|
+
name
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
... on ProjectV2ItemFieldIterationValue {
|
|
96
|
+
title
|
|
97
|
+
field {
|
|
98
|
+
... on ProjectV2Field{
|
|
99
|
+
name
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
content {
|
|
106
|
+
... on Issue {
|
|
107
|
+
number
|
|
108
|
+
title
|
|
109
|
+
state
|
|
110
|
+
url
|
|
111
|
+
createdAt
|
|
112
|
+
updatedAt
|
|
113
|
+
author {
|
|
114
|
+
login
|
|
115
|
+
}
|
|
116
|
+
labels(first: 100) {
|
|
117
|
+
nodes {
|
|
118
|
+
name
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
assignees(first: 20) {
|
|
122
|
+
nodes {
|
|
123
|
+
login
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
repository {
|
|
127
|
+
nameWithOwner
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
... on PullRequest {
|
|
131
|
+
number
|
|
132
|
+
title
|
|
133
|
+
state
|
|
134
|
+
url
|
|
135
|
+
createdAt
|
|
136
|
+
updatedAt
|
|
137
|
+
author {
|
|
138
|
+
login
|
|
139
|
+
}
|
|
140
|
+
labels(first: 100) {
|
|
141
|
+
nodes {
|
|
142
|
+
name
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
assignees(first: 20) {
|
|
146
|
+
nodes {
|
|
147
|
+
login
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
repository {
|
|
151
|
+
nameWithOwner
|
|
152
|
+
}
|
|
153
|
+
closingIssuesReferences(first: 50) {
|
|
154
|
+
nodes {
|
|
155
|
+
url
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}`;
|
|
24
160
|
export const PAGINATION_DELAY_MS = 5000;
|
|
25
161
|
export const FETCH_PROJECT_ITEMS_INITIAL_PAGE_SIZE = 100;
|
|
162
|
+
export const FETCH_PROJECT_ITEMS_BY_IDS_BATCH_SIZE = 100;
|
|
26
163
|
export const FETCH_PROJECT_ITEMS_GRAPHQL_ERROR_PAYLOAD_MAX_LENGTH = 4000;
|
|
27
164
|
export const RATE_LIMIT_MAX_RETRIES = 6;
|
|
28
165
|
export const RATE_LIMIT_MIN_BACKOFF_MS = 1000;
|
|
@@ -212,107 +349,7 @@ query GetProjectItems($projectId: ID!, $after: String, $first: Int!, $query: Str
|
|
|
212
349
|
hasNextPage
|
|
213
350
|
}
|
|
214
351
|
nodes {
|
|
215
|
-
id
|
|
216
|
-
fieldValues(first: 10) {
|
|
217
|
-
nodes {
|
|
218
|
-
... on ProjectV2ItemFieldTextValue {
|
|
219
|
-
text
|
|
220
|
-
field {
|
|
221
|
-
... on ProjectV2Field{
|
|
222
|
-
name
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
... on ProjectV2ItemFieldNumberValue {
|
|
227
|
-
number
|
|
228
|
-
id
|
|
229
|
-
field {
|
|
230
|
-
... on ProjectV2Field{
|
|
231
|
-
name
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
... on ProjectV2ItemFieldDateValue {
|
|
236
|
-
date
|
|
237
|
-
field {
|
|
238
|
-
... on ProjectV2Field{
|
|
239
|
-
name
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
... on ProjectV2ItemFieldSingleSelectValue {
|
|
244
|
-
name
|
|
245
|
-
field {
|
|
246
|
-
... on ProjectV2SingleSelectField {
|
|
247
|
-
name
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
... on ProjectV2ItemFieldIterationValue {
|
|
252
|
-
title
|
|
253
|
-
field {
|
|
254
|
-
... on ProjectV2Field{
|
|
255
|
-
name
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
content {
|
|
262
|
-
... on Issue {
|
|
263
|
-
number
|
|
264
|
-
title
|
|
265
|
-
state
|
|
266
|
-
url
|
|
267
|
-
createdAt
|
|
268
|
-
updatedAt
|
|
269
|
-
author {
|
|
270
|
-
login
|
|
271
|
-
}
|
|
272
|
-
labels(first: 100) {
|
|
273
|
-
nodes {
|
|
274
|
-
name
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
assignees(first: 20) {
|
|
278
|
-
nodes {
|
|
279
|
-
login
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
repository {
|
|
283
|
-
nameWithOwner
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
... on PullRequest {
|
|
287
|
-
number
|
|
288
|
-
title
|
|
289
|
-
state
|
|
290
|
-
url
|
|
291
|
-
createdAt
|
|
292
|
-
updatedAt
|
|
293
|
-
author {
|
|
294
|
-
login
|
|
295
|
-
}
|
|
296
|
-
labels(first: 100) {
|
|
297
|
-
nodes {
|
|
298
|
-
name
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
assignees(first: 20) {
|
|
302
|
-
nodes {
|
|
303
|
-
login
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
repository {
|
|
307
|
-
nameWithOwner
|
|
308
|
-
}
|
|
309
|
-
closingIssuesReferences(first: 50) {
|
|
310
|
-
nodes {
|
|
311
|
-
url
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
}
|
|
352
|
+
id${PROJECT_V2_ITEM_FIELD_VALUES_AND_CONTENT_SELECTION}
|
|
316
353
|
}
|
|
317
354
|
}
|
|
318
355
|
}
|
|
@@ -524,71 +561,12 @@ query GetProjectItems($projectId: ID!, $after: String, $first: Int!, $query: Str
|
|
|
524
561
|
console.log(
|
|
525
562
|
`fetchProjectItems: page ${pageIndex}, nodes: ${pageNodes.length}, cumulative: ${cumulativeRawNodes}/${totalCount}`,
|
|
526
563
|
);
|
|
527
|
-
const
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
number?: number;
|
|
533
|
-
date?: string;
|
|
534
|
-
name?: string;
|
|
535
|
-
field: {
|
|
536
|
-
name: string;
|
|
537
|
-
};
|
|
538
|
-
}[];
|
|
539
|
-
};
|
|
540
|
-
content: {
|
|
541
|
-
repository: { nameWithOwner: string };
|
|
542
|
-
number: number;
|
|
543
|
-
title: string;
|
|
544
|
-
state: string;
|
|
545
|
-
url: string;
|
|
546
|
-
createdAt: string;
|
|
547
|
-
updatedAt: string;
|
|
548
|
-
author: { login: string } | null;
|
|
549
|
-
labels: { nodes: { name: string }[] };
|
|
550
|
-
assignees: { nodes: { login: string }[] };
|
|
551
|
-
closingIssuesReferences?: { nodes: { url: string }[] };
|
|
552
|
-
};
|
|
553
|
-
}[] = pageNodes;
|
|
554
|
-
projectItems.forEach((item) => {
|
|
555
|
-
if (!item || !item.content || !item.content.repository) {
|
|
556
|
-
return;
|
|
564
|
+
const nodes: ProjectV2ItemNode[] = pageNodes;
|
|
565
|
+
nodes.forEach((item) => {
|
|
566
|
+
const projectItem = this.mapProjectV2ItemNodeToProjectItem(item);
|
|
567
|
+
if (projectItem) {
|
|
568
|
+
issues.push(projectItem);
|
|
557
569
|
}
|
|
558
|
-
issues.push({
|
|
559
|
-
id: item.id,
|
|
560
|
-
nameWithOwner: item.content.repository.nameWithOwner,
|
|
561
|
-
number: item.content.number,
|
|
562
|
-
title: item.content.title,
|
|
563
|
-
state: this.convertStrToState(item.content.state),
|
|
564
|
-
url: item.content.url,
|
|
565
|
-
body: null,
|
|
566
|
-
labels: item.content.labels?.nodes?.map((l) => l.name) || [],
|
|
567
|
-
assignees: item.content.assignees?.nodes?.map((a) => a.login) || [],
|
|
568
|
-
createdAt: item.content.createdAt || new Date().toISOString(),
|
|
569
|
-
updatedAt:
|
|
570
|
-
item.content.updatedAt ||
|
|
571
|
-
item.content.createdAt ||
|
|
572
|
-
new Date().toISOString(),
|
|
573
|
-
author: item.content.author?.login || '',
|
|
574
|
-
closingIssueReferenceUrls:
|
|
575
|
-
item.content.closingIssuesReferences?.nodes
|
|
576
|
-
?.map((node) => node.url)
|
|
577
|
-
.filter((url) => url.length > 0) || [],
|
|
578
|
-
customFields: item.fieldValues.nodes
|
|
579
|
-
.filter((field) => !!field.field)
|
|
580
|
-
.map((field) => {
|
|
581
|
-
return {
|
|
582
|
-
name: field.field.name,
|
|
583
|
-
value:
|
|
584
|
-
field.name ??
|
|
585
|
-
field.text ??
|
|
586
|
-
field.number?.toString() ??
|
|
587
|
-
field.date ??
|
|
588
|
-
null,
|
|
589
|
-
};
|
|
590
|
-
}),
|
|
591
|
-
});
|
|
592
570
|
});
|
|
593
571
|
if (
|
|
594
572
|
pageNodes.length > 0 &&
|
|
@@ -612,6 +590,263 @@ query GetProjectItems($projectId: ID!, $after: String, $first: Int!, $query: Str
|
|
|
612
590
|
}
|
|
613
591
|
return issues;
|
|
614
592
|
};
|
|
593
|
+
private mapProjectV2ItemNodeToProjectItem = (
|
|
594
|
+
item: ProjectV2ItemNode | null,
|
|
595
|
+
): ProjectItem | null => {
|
|
596
|
+
if (!item || !item.content || !item.content.repository) {
|
|
597
|
+
return null;
|
|
598
|
+
}
|
|
599
|
+
return {
|
|
600
|
+
id: item.id,
|
|
601
|
+
nameWithOwner: item.content.repository.nameWithOwner,
|
|
602
|
+
number: item.content.number,
|
|
603
|
+
title: item.content.title,
|
|
604
|
+
state: this.convertStrToState(item.content.state),
|
|
605
|
+
url: item.content.url,
|
|
606
|
+
body: null,
|
|
607
|
+
labels: item.content.labels?.nodes?.map((l) => l.name) || [],
|
|
608
|
+
assignees: item.content.assignees?.nodes?.map((a) => a.login) || [],
|
|
609
|
+
createdAt: item.content.createdAt || new Date().toISOString(),
|
|
610
|
+
updatedAt:
|
|
611
|
+
item.content.updatedAt ||
|
|
612
|
+
item.content.createdAt ||
|
|
613
|
+
new Date().toISOString(),
|
|
614
|
+
author: item.content.author?.login || '',
|
|
615
|
+
closingIssueReferenceUrls:
|
|
616
|
+
item.content.closingIssuesReferences?.nodes
|
|
617
|
+
?.map((node) => node.url)
|
|
618
|
+
.filter((url) => url.length > 0) || [],
|
|
619
|
+
customFields: item.fieldValues.nodes
|
|
620
|
+
.filter((field) => !!field.field)
|
|
621
|
+
.map((field) => {
|
|
622
|
+
return {
|
|
623
|
+
name: field.field.name,
|
|
624
|
+
value:
|
|
625
|
+
field.name ??
|
|
626
|
+
field.text ??
|
|
627
|
+
field.number?.toString() ??
|
|
628
|
+
field.date ??
|
|
629
|
+
null,
|
|
630
|
+
};
|
|
631
|
+
}),
|
|
632
|
+
};
|
|
633
|
+
};
|
|
634
|
+
fetchProjectItemsLight = async (
|
|
635
|
+
projectId: string,
|
|
636
|
+
query?: string,
|
|
637
|
+
): Promise<ProjectItemLight[]> => {
|
|
638
|
+
const graphqlQueryString = `
|
|
639
|
+
query GetProjectItemsLight($projectId: ID!, $after: String, $first: Int!, $query: String) {
|
|
640
|
+
node(id: $projectId) {
|
|
641
|
+
... on ProjectV2 {
|
|
642
|
+
items(first: $first, after: $after, query: $query) {
|
|
643
|
+
totalCount
|
|
644
|
+
pageInfo {
|
|
645
|
+
endCursor
|
|
646
|
+
hasNextPage
|
|
647
|
+
}
|
|
648
|
+
nodes {
|
|
649
|
+
id
|
|
650
|
+
updatedAt
|
|
651
|
+
content {
|
|
652
|
+
... on Issue {
|
|
653
|
+
url
|
|
654
|
+
number
|
|
655
|
+
}
|
|
656
|
+
... on PullRequest {
|
|
657
|
+
url
|
|
658
|
+
number
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
`;
|
|
667
|
+
const callGraphql = async (
|
|
668
|
+
after: string | null,
|
|
669
|
+
): Promise<{
|
|
670
|
+
totalCount: number;
|
|
671
|
+
pageInfo: { endCursor: string; hasNextPage: boolean };
|
|
672
|
+
nodes: {
|
|
673
|
+
id: string;
|
|
674
|
+
updatedAt: string;
|
|
675
|
+
content: { url: string; number: number } | null;
|
|
676
|
+
}[];
|
|
677
|
+
}> => {
|
|
678
|
+
const response = await callWithRateLimitRetry(() =>
|
|
679
|
+
ky
|
|
680
|
+
.post('https://api.github.com/graphql', {
|
|
681
|
+
json: {
|
|
682
|
+
query: graphqlQueryString,
|
|
683
|
+
variables: {
|
|
684
|
+
projectId: projectId,
|
|
685
|
+
after: after,
|
|
686
|
+
first: FETCH_PROJECT_ITEMS_INITIAL_PAGE_SIZE,
|
|
687
|
+
query: query ?? null,
|
|
688
|
+
},
|
|
689
|
+
},
|
|
690
|
+
headers: {
|
|
691
|
+
Authorization: `Bearer ${this.ghToken}`,
|
|
692
|
+
},
|
|
693
|
+
})
|
|
694
|
+
.json<{
|
|
695
|
+
data: {
|
|
696
|
+
node: {
|
|
697
|
+
items: {
|
|
698
|
+
totalCount: number;
|
|
699
|
+
pageInfo: { endCursor: string; hasNextPage: boolean };
|
|
700
|
+
nodes: {
|
|
701
|
+
id: string;
|
|
702
|
+
updatedAt: string;
|
|
703
|
+
content: { url: string; number: number } | null;
|
|
704
|
+
}[];
|
|
705
|
+
};
|
|
706
|
+
} | null;
|
|
707
|
+
} | null;
|
|
708
|
+
errors?: { message: string }[];
|
|
709
|
+
}>(),
|
|
710
|
+
);
|
|
711
|
+
if (response.errors && response.errors.length > 0) {
|
|
712
|
+
throw new Error(
|
|
713
|
+
`GitHub GraphQL errors: ${stringifyGraphqlErrorsForLog(response.errors)}`,
|
|
714
|
+
);
|
|
715
|
+
}
|
|
716
|
+
const rawData = response.data;
|
|
717
|
+
if (!rawData || rawData.node === null) {
|
|
718
|
+
throw new Error('No data returned from GitHub API');
|
|
719
|
+
}
|
|
720
|
+
return rawData.node.items;
|
|
721
|
+
};
|
|
722
|
+
const lightItems: ProjectItemLight[] = [];
|
|
723
|
+
let after: string | null = null;
|
|
724
|
+
let hasNextPage = true;
|
|
725
|
+
let totalCount = 0;
|
|
726
|
+
let cumulativeRawNodes = 0;
|
|
727
|
+
let pageIndex = 0;
|
|
728
|
+
while (hasNextPage) {
|
|
729
|
+
if (after !== null) {
|
|
730
|
+
await new Promise((resolve) =>
|
|
731
|
+
setTimeout(resolve, PAGINATION_DELAY_MS),
|
|
732
|
+
);
|
|
733
|
+
}
|
|
734
|
+
const items = await callGraphql(after);
|
|
735
|
+
const pageNodes = items.nodes;
|
|
736
|
+
const pageInfo = items.pageInfo;
|
|
737
|
+
totalCount = items.totalCount;
|
|
738
|
+
cumulativeRawNodes += pageNodes.length;
|
|
739
|
+
pageIndex++;
|
|
740
|
+
console.log(
|
|
741
|
+
`fetchProjectItemsLight: page ${pageIndex}, nodes: ${pageNodes.length}, cumulative: ${cumulativeRawNodes}/${totalCount}`,
|
|
742
|
+
);
|
|
743
|
+
pageNodes.forEach((node) => {
|
|
744
|
+
if (!node || !node.content || !node.content.url) {
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
lightItems.push({
|
|
748
|
+
id: node.id,
|
|
749
|
+
updatedAt: node.updatedAt,
|
|
750
|
+
url: node.content.url,
|
|
751
|
+
number: node.content.number,
|
|
752
|
+
});
|
|
753
|
+
});
|
|
754
|
+
if (
|
|
755
|
+
pageNodes.length > 0 &&
|
|
756
|
+
!pageInfo.hasNextPage &&
|
|
757
|
+
cumulativeRawNodes < totalCount
|
|
758
|
+
) {
|
|
759
|
+
throw new Error(
|
|
760
|
+
`fetchProjectItemsLight: page ${pageIndex} has ${pageNodes.length} nodes with hasNextPage=false but only ${cumulativeRawNodes}/${totalCount} items accumulated`,
|
|
761
|
+
);
|
|
762
|
+
}
|
|
763
|
+
hasNextPage = pageInfo.hasNextPage;
|
|
764
|
+
after = pageInfo.endCursor;
|
|
765
|
+
}
|
|
766
|
+
console.log(
|
|
767
|
+
`fetchProjectItemsLight: completed, totalCount: ${totalCount}, cumulativeRawNodes: ${cumulativeRawNodes}, items: ${lightItems.length}`,
|
|
768
|
+
);
|
|
769
|
+
if (cumulativeRawNodes !== totalCount) {
|
|
770
|
+
throw new Error(
|
|
771
|
+
`fetchProjectItemsLight: expected ${totalCount} items but accumulated ${cumulativeRawNodes}`,
|
|
772
|
+
);
|
|
773
|
+
}
|
|
774
|
+
return lightItems;
|
|
775
|
+
};
|
|
776
|
+
fetchProjectItemsByIds = async (ids: string[]): Promise<ProjectItem[]> => {
|
|
777
|
+
if (ids.length === 0) {
|
|
778
|
+
return [];
|
|
779
|
+
}
|
|
780
|
+
const graphqlQueryString = `
|
|
781
|
+
query GetProjectItemsByIds($ids: [ID!]!) {
|
|
782
|
+
nodes(ids: $ids) {
|
|
783
|
+
... on ProjectV2Item {
|
|
784
|
+
id${PROJECT_V2_ITEM_FIELD_VALUES_AND_CONTENT_SELECTION}
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
`;
|
|
789
|
+
const callGraphql = async (
|
|
790
|
+
batchIds: string[],
|
|
791
|
+
): Promise<(ProjectV2ItemNode | null)[]> => {
|
|
792
|
+
const response = await callWithRateLimitRetry(() =>
|
|
793
|
+
ky
|
|
794
|
+
.post('https://api.github.com/graphql', {
|
|
795
|
+
json: {
|
|
796
|
+
query: graphqlQueryString,
|
|
797
|
+
variables: {
|
|
798
|
+
ids: batchIds,
|
|
799
|
+
},
|
|
800
|
+
},
|
|
801
|
+
headers: {
|
|
802
|
+
Authorization: `Bearer ${this.ghToken}`,
|
|
803
|
+
},
|
|
804
|
+
})
|
|
805
|
+
.json<{
|
|
806
|
+
data: { nodes: (ProjectV2ItemNode | null)[] } | null;
|
|
807
|
+
errors?: { message: string }[];
|
|
808
|
+
}>(),
|
|
809
|
+
);
|
|
810
|
+
if (response.errors && response.errors.length > 0) {
|
|
811
|
+
throw new Error(
|
|
812
|
+
`GitHub GraphQL errors: ${stringifyGraphqlErrorsForLog(response.errors)}`,
|
|
813
|
+
);
|
|
814
|
+
}
|
|
815
|
+
if (!response.data) {
|
|
816
|
+
throw new Error('No data returned from GitHub API');
|
|
817
|
+
}
|
|
818
|
+
return response.data.nodes;
|
|
819
|
+
};
|
|
820
|
+
const items: ProjectItem[] = [];
|
|
821
|
+
let batchIndex = 0;
|
|
822
|
+
for (
|
|
823
|
+
let start = 0;
|
|
824
|
+
start < ids.length;
|
|
825
|
+
start += FETCH_PROJECT_ITEMS_BY_IDS_BATCH_SIZE
|
|
826
|
+
) {
|
|
827
|
+
if (start > 0) {
|
|
828
|
+
await new Promise((resolve) =>
|
|
829
|
+
setTimeout(resolve, PAGINATION_DELAY_MS),
|
|
830
|
+
);
|
|
831
|
+
}
|
|
832
|
+
const batchIds = ids.slice(
|
|
833
|
+
start,
|
|
834
|
+
start + FETCH_PROJECT_ITEMS_BY_IDS_BATCH_SIZE,
|
|
835
|
+
);
|
|
836
|
+
const nodes = await callGraphql(batchIds);
|
|
837
|
+
batchIndex++;
|
|
838
|
+
console.log(
|
|
839
|
+
`fetchProjectItemsByIds: batch ${batchIndex}, ids: ${batchIds.length}, nodes: ${nodes.length}`,
|
|
840
|
+
);
|
|
841
|
+
nodes.forEach((node) => {
|
|
842
|
+
const projectItem = this.mapProjectV2ItemNodeToProjectItem(node);
|
|
843
|
+
if (projectItem) {
|
|
844
|
+
items.push(projectItem);
|
|
845
|
+
}
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
return items;
|
|
849
|
+
};
|
|
615
850
|
getProjectItemFieldsFromIssueUrl = async (
|
|
616
851
|
issueUrl: string,
|
|
617
852
|
): Promise<
|
|
@@ -97,6 +97,7 @@ describe('GenerateInTmuxByHumanDataUseCase', () => {
|
|
|
97
97
|
project?: Project;
|
|
98
98
|
consoleBaseUrl?: string | null;
|
|
99
99
|
consoleToken?: string | null;
|
|
100
|
+
newIssueRepo?: string;
|
|
100
101
|
} = {},
|
|
101
102
|
) =>
|
|
102
103
|
usecase.run({
|
|
@@ -106,6 +107,7 @@ describe('GenerateInTmuxByHumanDataUseCase', () => {
|
|
|
106
107
|
assigneeLogin: ASSIGNEE,
|
|
107
108
|
org: 'demo-org',
|
|
108
109
|
repo: 'demo-repo',
|
|
110
|
+
newIssueRepo: overrides.newIssueRepo,
|
|
109
111
|
consoleBaseUrl:
|
|
110
112
|
overrides.consoleBaseUrl === undefined
|
|
111
113
|
? CONSOLE_BASE_URL
|
|
@@ -292,6 +294,15 @@ describe('GenerateInTmuxByHumanDataUseCase', () => {
|
|
|
292
294
|
});
|
|
293
295
|
});
|
|
294
296
|
|
|
297
|
+
it('overrides only the repo segment of the new issue url with newIssueRepo, leaving org and assignee unchanged', () => {
|
|
298
|
+
const result = run([makeIssue({ story: 'Story Alpha' })], {
|
|
299
|
+
newIssueRepo: 'other-repo',
|
|
300
|
+
});
|
|
301
|
+
expect(result.v4?.newIssueUrl).toBe(
|
|
302
|
+
'https://github.com/demo-org/other-repo/issues/new?assignees=owner-login',
|
|
303
|
+
);
|
|
304
|
+
});
|
|
305
|
+
|
|
295
306
|
it('maps each session to the issue url as name and the issue title as description', () => {
|
|
296
307
|
const result = run([
|
|
297
308
|
makeIssue({ story: 'Story Alpha' }),
|
|
@@ -55,6 +55,7 @@ export type GenerateInTmuxByHumanDataInput = {
|
|
|
55
55
|
assigneeLogin: string;
|
|
56
56
|
org: string;
|
|
57
57
|
repo: string;
|
|
58
|
+
newIssueRepo?: string;
|
|
58
59
|
consoleBaseUrl: string | null;
|
|
59
60
|
consoleToken: string | null;
|
|
60
61
|
now: Date;
|
|
@@ -77,6 +78,7 @@ export class GenerateInTmuxByHumanDataUseCase {
|
|
|
77
78
|
assigneeLogin,
|
|
78
79
|
org,
|
|
79
80
|
repo,
|
|
81
|
+
newIssueRepo,
|
|
80
82
|
consoleBaseUrl,
|
|
81
83
|
consoleToken,
|
|
82
84
|
} = input;
|
|
@@ -132,7 +134,7 @@ export class GenerateInTmuxByHumanDataUseCase {
|
|
|
132
134
|
version: 4,
|
|
133
135
|
overviewUrl,
|
|
134
136
|
tdpmConsoleUrl: `${tdpmConsoleUrl}?k=${consoleToken}`,
|
|
135
|
-
newIssueUrl: `https://github.com/${org}/${repo}/issues/new?assignees=${assigneeLogin}`,
|
|
137
|
+
newIssueUrl: `https://github.com/${org}/${newIssueRepo ?? repo}/issues/new?assignees=${assigneeLogin}`,
|
|
136
138
|
groups: v4Groups,
|
|
137
139
|
}
|
|
138
140
|
: null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HandleScheduledEventUseCaseHandler.d.ts","sourceRoot":"","sources":["../../../../src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts"],"names":[],"mappings":"AAkCA,OAAO,EAAE,KAAK,EAAE,MAAM,gCAAgC,CAAC;AACvD,OAAO,EAAE,OAAO,EAAE,MAAM,kCAAkC,CAAC;AAiD3D,qBAAa,kCAAkC;IAC7C,MAAM,GACJ,gBAAgB,MAAM,EACtB,UAAU,OAAO,KAChB,OAAO,CAAC;QACT,OAAO,EAAE,OAAO,CAAC;QACjB,MAAM,EAAE,KAAK,EAAE,CAAC;QAChB,SAAS,EAAE,OAAO,CAAC;QACnB,eAAe,EAAE,IAAI,EAAE,CAAC;KACzB,GAAG,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"HandleScheduledEventUseCaseHandler.d.ts","sourceRoot":"","sources":["../../../../src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts"],"names":[],"mappings":"AAkCA,OAAO,EAAE,KAAK,EAAE,MAAM,gCAAgC,CAAC;AACvD,OAAO,EAAE,OAAO,EAAE,MAAM,kCAAkC,CAAC;AAiD3D,qBAAa,kCAAkC;IAC7C,MAAM,GACJ,gBAAgB,MAAM,EACtB,UAAU,OAAO,KAChB,OAAO,CAAC;QACT,OAAO,EAAE,OAAO,CAAC;QACjB,MAAM,EAAE,KAAK,EAAE,CAAC;QAChB,SAAS,EAAE,OAAO,CAAC;QACnB,eAAe,EAAE,IAAI,EAAE,CAAC;KACzB,GAAG,IAAI,CAAC,CAmkBP;CACH"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inTmuxByHumanDataWriter.d.ts","sourceRoot":"","sources":["../../../../src/adapter/entry-points/handlers/inTmuxByHumanDataWriter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gCAAgC,CAAC;AAC5D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kCAAkC,CAAC;AAMhE,MAAM,MAAM,6BAA6B,GAAG;IAC1C,mBAAmB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAC/C,oBAAoB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAChD,kBAAkB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAC9C,kBAAkB,EAAE,MAAM,EAAE,GAAG,IAAI,GAAG,SAAS,CAAC;IAChD,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAClC,aAAa,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACzC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,GAAG,EAAE,IAAI,CAAC;CACX,CAAC;AAUF,eAAO,MAAM,sBAAsB,GACjC,QAAQ,6BAA6B,KACpC,
|
|
1
|
+
{"version":3,"file":"inTmuxByHumanDataWriter.d.ts","sourceRoot":"","sources":["../../../../src/adapter/entry-points/handlers/inTmuxByHumanDataWriter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gCAAgC,CAAC;AAC5D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kCAAkC,CAAC;AAMhE,MAAM,MAAM,6BAA6B,GAAG;IAC1C,mBAAmB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAC/C,oBAAoB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAChD,kBAAkB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAC9C,kBAAkB,EAAE,MAAM,EAAE,GAAG,IAAI,GAAG,SAAS,CAAC;IAChD,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAClC,aAAa,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACzC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACzC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,GAAG,EAAE,IAAI,CAAC;CACX,CAAC;AAUF,eAAO,MAAM,sBAAsB,GACjC,QAAQ,6BAA6B,KACpC,IA0EF,CAAC"}
|
|
@@ -13,6 +13,7 @@ import { ProjectRepository } from '../../../domain/usecases/adapter-interfaces/P
|
|
|
13
13
|
import { DateRepository } from '../../../domain/usecases/adapter-interfaces/DateRepository';
|
|
14
14
|
import { Sleep } from './githubRateLimitRetry';
|
|
15
15
|
export declare const FULL_ISSUE_FETCH_INTERVAL_MS: number;
|
|
16
|
+
export declare const INCREMENTAL_FETCH_SKEW_BUFFER_MS: number;
|
|
16
17
|
export type CachedProjectIssues = {
|
|
17
18
|
lastFetchedAt: string;
|
|
18
19
|
lastFullFetchAt: string;
|
|
@@ -22,14 +23,14 @@ export type CachedProjectIssues = {
|
|
|
22
23
|
export declare class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository implements IssueRepository {
|
|
23
24
|
readonly apiV3IssueRepository: Pick<ApiV3IssueRepository, 'searchIssue'>;
|
|
24
25
|
readonly restIssueRepository: Pick<RestIssueRepository, 'createNewIssue' | 'updateIssue' | 'createComment' | 'getIssue' | 'updateLabels' | 'removeLabel' | 'updateAssigneeList'>;
|
|
25
|
-
readonly graphqlProjectItemRepository: Pick<GraphqlProjectItemRepository, 'fetchProjectItems' | 'fetchProjectItemByUrl' | 'updateProjectField' | 'clearProjectField' | 'updateProjectTextField' | 'addIssueToProject'>;
|
|
26
|
+
readonly graphqlProjectItemRepository: Pick<GraphqlProjectItemRepository, 'fetchProjectItems' | 'fetchProjectItemsLight' | 'fetchProjectItemsByIds' | 'fetchProjectItemByUrl' | 'updateProjectField' | 'clearProjectField' | 'updateProjectTextField' | 'addIssueToProject'>;
|
|
26
27
|
readonly localStorageCacheRepository: Pick<LocalStorageCacheRepository, 'getSingle' | 'setSingle'>;
|
|
27
28
|
readonly projectRepository: Pick<ProjectRepository, 'getProject'>;
|
|
28
29
|
readonly dateRepository: DateRepository;
|
|
29
30
|
readonly localStorageRepository: LocalStorageRepository;
|
|
30
31
|
readonly ghToken: string;
|
|
31
32
|
readonly sleep: Sleep;
|
|
32
|
-
constructor(apiV3IssueRepository: Pick<ApiV3IssueRepository, 'searchIssue'>, restIssueRepository: Pick<RestIssueRepository, 'createNewIssue' | 'updateIssue' | 'createComment' | 'getIssue' | 'updateLabels' | 'removeLabel' | 'updateAssigneeList'>, graphqlProjectItemRepository: Pick<GraphqlProjectItemRepository, 'fetchProjectItems' | 'fetchProjectItemByUrl' | 'updateProjectField' | 'clearProjectField' | 'updateProjectTextField' | 'addIssueToProject'>, localStorageCacheRepository: Pick<LocalStorageCacheRepository, 'getSingle' | 'setSingle'>, projectRepository: Pick<ProjectRepository, 'getProject'>, dateRepository: DateRepository, localStorageRepository: LocalStorageRepository, ghToken?: string, sleep?: Sleep);
|
|
33
|
+
constructor(apiV3IssueRepository: Pick<ApiV3IssueRepository, 'searchIssue'>, restIssueRepository: Pick<RestIssueRepository, 'createNewIssue' | 'updateIssue' | 'createComment' | 'getIssue' | 'updateLabels' | 'removeLabel' | 'updateAssigneeList'>, graphqlProjectItemRepository: Pick<GraphqlProjectItemRepository, 'fetchProjectItems' | 'fetchProjectItemsLight' | 'fetchProjectItemsByIds' | 'fetchProjectItemByUrl' | 'updateProjectField' | 'clearProjectField' | 'updateProjectTextField' | 'addIssueToProject'>, localStorageCacheRepository: Pick<LocalStorageCacheRepository, 'getSingle' | 'setSingle'>, projectRepository: Pick<ProjectRepository, 'getProject'>, dateRepository: DateRepository, localStorageRepository: LocalStorageRepository, ghToken?: string, sleep?: Sleep);
|
|
33
34
|
private readonly getAllIssuesRefreshMemo;
|
|
34
35
|
private fetchWithRateLimitRetry;
|
|
35
36
|
updateStatus: (project: Project, issue: Issue, statusId: string) => Promise<void>;
|