github-issue-tower-defence-management 1.50.1 → 1.50.3
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 +15 -0
- package/bin/adapter/repositories/GitHubIssueCommentRepository.js +5 -83
- package/bin/adapter/repositories/GitHubIssueCommentRepository.js.map +1 -1
- package/bin/adapter/repositories/GraphqlProjectRepository.js +12 -0
- package/bin/adapter/repositories/GraphqlProjectRepository.js.map +1 -1
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js +1 -1
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js.map +1 -1
- package/bin/adapter/repositories/issue/GraphqlProjectItemRepository.js +29 -0
- package/bin/adapter/repositories/issue/GraphqlProjectItemRepository.js.map +1 -1
- package/bin/domain/usecases/StartPreparationUseCase.js +0 -1
- package/bin/domain/usecases/StartPreparationUseCase.js.map +1 -1
- package/package.json +1 -1
- package/src/adapter/repositories/GitHubIssueCommentRepository.test.ts +102 -0
- package/src/adapter/repositories/GitHubIssueCommentRepository.ts +13 -134
- package/src/adapter/repositories/GraphqlProjectRepository.errorHandling.test.ts +112 -0
- package/src/adapter/repositories/GraphqlProjectRepository.ts +20 -2
- package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.test.ts +3 -1
- package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts +1 -1
- package/src/adapter/repositories/issue/GraphqlProjectItemRepository.ts +46 -3
- package/src/domain/usecases/StartPreparationUseCase.test.ts +21 -26
- package/src/domain/usecases/StartPreparationUseCase.ts +0 -1
- package/types/adapter/repositories/GitHubIssueCommentRepository.d.ts +0 -1
- package/types/adapter/repositories/GitHubIssueCommentRepository.d.ts.map +1 -1
- package/types/adapter/repositories/GraphqlProjectRepository.d.ts.map +1 -1
- package/types/adapter/repositories/issue/GraphqlProjectItemRepository.d.ts +1 -0
- package/types/adapter/repositories/issue/GraphqlProjectItemRepository.d.ts.map +1 -1
- package/types/domain/usecases/StartPreparationUseCase.d.ts.map +1 -1
|
@@ -8,32 +8,6 @@ type RestCommentPayload = {
|
|
|
8
8
|
created_at: string;
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
-
type CreateCommentResponse = {
|
|
12
|
-
data?: {
|
|
13
|
-
addComment?: {
|
|
14
|
-
commentEdge: {
|
|
15
|
-
node: {
|
|
16
|
-
id: string;
|
|
17
|
-
};
|
|
18
|
-
};
|
|
19
|
-
};
|
|
20
|
-
};
|
|
21
|
-
errors?: Array<{ message: string }>;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
type IssueIdResponse = {
|
|
25
|
-
data?: {
|
|
26
|
-
repository?: {
|
|
27
|
-
issue?: {
|
|
28
|
-
id: string;
|
|
29
|
-
};
|
|
30
|
-
pullRequest?: {
|
|
31
|
-
id: string;
|
|
32
|
-
};
|
|
33
|
-
};
|
|
34
|
-
};
|
|
35
|
-
};
|
|
36
|
-
|
|
37
11
|
function isRestCommentPayloadArray(
|
|
38
12
|
value: unknown,
|
|
39
13
|
): value is RestCommentPayload[] {
|
|
@@ -41,18 +15,6 @@ function isRestCommentPayloadArray(
|
|
|
41
15
|
return true;
|
|
42
16
|
}
|
|
43
17
|
|
|
44
|
-
function isCreateCommentResponse(
|
|
45
|
-
value: unknown,
|
|
46
|
-
): value is CreateCommentResponse {
|
|
47
|
-
if (typeof value !== 'object' || value === null) return false;
|
|
48
|
-
return true;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function isIssueIdResponse(value: unknown): value is IssueIdResponse {
|
|
52
|
-
if (typeof value !== 'object' || value === null) return false;
|
|
53
|
-
return true;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
18
|
export class GitHubIssueCommentRepository implements IssueCommentRepository {
|
|
57
19
|
constructor(private readonly token: string) {}
|
|
58
20
|
|
|
@@ -122,108 +84,25 @@ export class GitHubIssueCommentRepository implements IssueCommentRepository {
|
|
|
122
84
|
return comments;
|
|
123
85
|
}
|
|
124
86
|
|
|
125
|
-
private async getIssueNodeId(issue: Issue): Promise<string> {
|
|
126
|
-
const { owner, repo, issueNumber, isPr } = this.parseIssueUrl(issue);
|
|
127
|
-
|
|
128
|
-
const entityType = isPr ? 'pullRequest' : 'issue';
|
|
129
|
-
const query = `
|
|
130
|
-
query($owner: String!, $repo: String!, $issueNumber: Int!) {
|
|
131
|
-
repository(owner: $owner, name: $repo) {
|
|
132
|
-
${entityType}(number: $issueNumber) {
|
|
133
|
-
id
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
`;
|
|
138
|
-
|
|
139
|
-
const response = await fetch('https://api.github.com/graphql', {
|
|
140
|
-
method: 'POST',
|
|
141
|
-
headers: {
|
|
142
|
-
Authorization: `Bearer ${this.token}`,
|
|
143
|
-
'Content-Type': 'application/json',
|
|
144
|
-
},
|
|
145
|
-
body: JSON.stringify({
|
|
146
|
-
query,
|
|
147
|
-
variables: {
|
|
148
|
-
owner,
|
|
149
|
-
repo,
|
|
150
|
-
issueNumber,
|
|
151
|
-
},
|
|
152
|
-
}),
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
if (!response.ok) {
|
|
156
|
-
throw new Error(
|
|
157
|
-
`Failed to fetch issue ID from GitHub GraphQL API: ${response.status} ${response.statusText}`,
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const responseData: unknown = await response.json();
|
|
162
|
-
if (!isIssueIdResponse(responseData)) {
|
|
163
|
-
throw new Error(
|
|
164
|
-
'Unexpected response shape when fetching issue ID from GitHub GraphQL API',
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const issueId = isPr
|
|
169
|
-
? responseData.data?.repository?.pullRequest?.id
|
|
170
|
-
: responseData.data?.repository?.issue?.id;
|
|
171
|
-
if (!issueId) {
|
|
172
|
-
throw new Error(
|
|
173
|
-
`${isPr ? 'Pull request' : 'Issue'} not found when fetching issue ID from GitHub GraphQL API`,
|
|
174
|
-
);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
return issueId;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
87
|
async createComment(issue: Issue, commentContent: string): Promise<void> {
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
const mutation = `
|
|
184
|
-
mutation($issueId: ID!, $body: String!) {
|
|
185
|
-
addComment(input: {
|
|
186
|
-
subjectId: $issueId
|
|
187
|
-
body: $body
|
|
188
|
-
}) {
|
|
189
|
-
commentEdge {
|
|
190
|
-
node {
|
|
191
|
-
id
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
`;
|
|
88
|
+
const { owner, repo, issueNumber } = this.parseIssueUrl(issue);
|
|
197
89
|
|
|
198
|
-
const response = await fetch(
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
variables: {
|
|
207
|
-
issueId,
|
|
208
|
-
body: commentContent,
|
|
90
|
+
const response = await fetch(
|
|
91
|
+
`https://api.github.com/repos/${owner}/${repo}/issues/${issueNumber}/comments`,
|
|
92
|
+
{
|
|
93
|
+
method: 'POST',
|
|
94
|
+
headers: {
|
|
95
|
+
Authorization: `Bearer ${this.token}`,
|
|
96
|
+
Accept: 'application/vnd.github+json',
|
|
97
|
+
'Content-Type': 'application/json',
|
|
209
98
|
},
|
|
210
|
-
|
|
211
|
-
|
|
99
|
+
body: JSON.stringify({ body: commentContent }),
|
|
100
|
+
},
|
|
101
|
+
);
|
|
212
102
|
|
|
213
103
|
if (!response.ok) {
|
|
214
104
|
throw new Error(
|
|
215
|
-
`Failed to create comment via GitHub
|
|
216
|
-
);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
const responseData: unknown = await response.json();
|
|
220
|
-
if (!isCreateCommentResponse(responseData)) {
|
|
221
|
-
throw new Error('Invalid API response format when creating comment');
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
if (responseData.errors) {
|
|
225
|
-
throw new Error(
|
|
226
|
-
`GraphQL errors when creating comment: ${JSON.stringify(responseData.errors)}`,
|
|
105
|
+
`Failed to create comment via GitHub REST API: ${response.status} ${response.statusText}`,
|
|
227
106
|
);
|
|
228
107
|
}
|
|
229
108
|
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
const mockPost = jest.fn();
|
|
2
|
+
|
|
3
|
+
jest.mock('ky', () => ({
|
|
4
|
+
default: {
|
|
5
|
+
post: mockPost,
|
|
6
|
+
get: jest.fn(),
|
|
7
|
+
put: jest.fn(),
|
|
8
|
+
patch: jest.fn(),
|
|
9
|
+
delete: jest.fn(),
|
|
10
|
+
extend: jest.fn(),
|
|
11
|
+
create: jest.fn(),
|
|
12
|
+
stop: jest.fn(),
|
|
13
|
+
},
|
|
14
|
+
__esModule: true,
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
import { GraphqlProjectRepository } from './GraphqlProjectRepository';
|
|
18
|
+
import { LocalStorageRepository } from './LocalStorageRepository';
|
|
19
|
+
|
|
20
|
+
const mockJsonResponse = <T>(data: T) => ({
|
|
21
|
+
json: jest.fn().mockResolvedValue(data),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('GraphqlProjectRepository error handling', () => {
|
|
25
|
+
let repository: GraphqlProjectRepository;
|
|
26
|
+
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
mockPost.mockClear();
|
|
29
|
+
repository = new GraphqlProjectRepository(
|
|
30
|
+
new LocalStorageRepository(),
|
|
31
|
+
'',
|
|
32
|
+
'dummy-token',
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe('fetchProjectId', () => {
|
|
37
|
+
it('should throw a descriptive error when response has no data field', async () => {
|
|
38
|
+
mockPost.mockReturnValueOnce(mockJsonResponse({}));
|
|
39
|
+
|
|
40
|
+
await expect(repository.fetchProjectId('someOrg', 1)).rejects.toThrow(
|
|
41
|
+
'GitHub GraphQL API returned no data for fetchProjectId: no data field in response',
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should throw with error messages when response contains only errors', async () => {
|
|
46
|
+
mockPost.mockReturnValueOnce(
|
|
47
|
+
mockJsonResponse({
|
|
48
|
+
errors: [
|
|
49
|
+
{ message: 'was submitted too quickly' },
|
|
50
|
+
{ message: 'secondary rate limit' },
|
|
51
|
+
],
|
|
52
|
+
}),
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
await expect(repository.fetchProjectId('someOrg', 1)).rejects.toThrow(
|
|
56
|
+
'GitHub GraphQL API returned no data for fetchProjectId: was submitted too quickly; secondary rate limit',
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should return project ID when data is present for organization', async () => {
|
|
61
|
+
mockPost.mockReturnValueOnce(
|
|
62
|
+
mockJsonResponse({
|
|
63
|
+
data: {
|
|
64
|
+
organization: {
|
|
65
|
+
projectV2: { id: 'org-project-id', databaseId: 1 },
|
|
66
|
+
},
|
|
67
|
+
user: { projectV2: null },
|
|
68
|
+
},
|
|
69
|
+
}),
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const result = await repository.fetchProjectId('someOrg', 1);
|
|
73
|
+
expect(result).toBe('org-project-id');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should return project ID when data is present for user', async () => {
|
|
77
|
+
mockPost.mockReturnValueOnce(
|
|
78
|
+
mockJsonResponse({
|
|
79
|
+
data: {
|
|
80
|
+
organization: { projectV2: null },
|
|
81
|
+
user: { projectV2: { id: 'user-project-id', databaseId: 2 } },
|
|
82
|
+
},
|
|
83
|
+
}),
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const result = await repository.fetchProjectId('someUser', 2);
|
|
87
|
+
expect(result).toBe('user-project-id');
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('getProject', () => {
|
|
92
|
+
it('should throw a descriptive error when response has no data field', async () => {
|
|
93
|
+
mockPost.mockReturnValueOnce(mockJsonResponse({}));
|
|
94
|
+
|
|
95
|
+
await expect(repository.getProject('project-id')).rejects.toThrow(
|
|
96
|
+
'GitHub GraphQL API returned no data for getProject: no data field in response',
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should throw with error messages when response contains only errors', async () => {
|
|
101
|
+
mockPost.mockReturnValueOnce(
|
|
102
|
+
mockJsonResponse({
|
|
103
|
+
errors: [{ message: 'abuse detection triggered' }],
|
|
104
|
+
}),
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
await expect(repository.getProject('project-id')).rejects.toThrow(
|
|
108
|
+
'GitHub GraphQL API returned no data for getProject: abuse detection triggered',
|
|
109
|
+
);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
});
|
|
@@ -61,7 +61,7 @@ export class GraphqlProjectRepository
|
|
|
61
61
|
},
|
|
62
62
|
})
|
|
63
63
|
.json<{
|
|
64
|
-
data
|
|
64
|
+
data?: {
|
|
65
65
|
organization: {
|
|
66
66
|
projectV2: {
|
|
67
67
|
id: string;
|
|
@@ -75,8 +75,17 @@ export class GraphqlProjectRepository
|
|
|
75
75
|
};
|
|
76
76
|
};
|
|
77
77
|
};
|
|
78
|
+
errors?: { message: string }[];
|
|
78
79
|
}>();
|
|
79
80
|
|
|
81
|
+
if (!response.data) {
|
|
82
|
+
const errorMessages = response.errors
|
|
83
|
+
? response.errors.map((e) => e.message).join('; ')
|
|
84
|
+
: 'no data field in response';
|
|
85
|
+
throw new Error(
|
|
86
|
+
`GitHub GraphQL API returned no data for fetchProjectId: ${errorMessages}`,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
80
89
|
const projectId =
|
|
81
90
|
response.data.organization?.projectV2?.id ||
|
|
82
91
|
response.data.user?.projectV2?.id;
|
|
@@ -156,7 +165,7 @@ export class GraphqlProjectRepository
|
|
|
156
165
|
},
|
|
157
166
|
})
|
|
158
167
|
.json<{
|
|
159
|
-
data
|
|
168
|
+
data?: {
|
|
160
169
|
node: {
|
|
161
170
|
id: string;
|
|
162
171
|
databaseId: number;
|
|
@@ -191,7 +200,16 @@ export class GraphqlProjectRepository
|
|
|
191
200
|
};
|
|
192
201
|
};
|
|
193
202
|
};
|
|
203
|
+
errors?: { message: string }[];
|
|
194
204
|
}>();
|
|
205
|
+
if (!response.data) {
|
|
206
|
+
const errorMessages = response.errors
|
|
207
|
+
? response.errors.map((e) => e.message).join('; ')
|
|
208
|
+
: 'no data field in response';
|
|
209
|
+
throw new Error(
|
|
210
|
+
`GitHub GraphQL API returned no data for getProject: ${errorMessages}`,
|
|
211
|
+
);
|
|
212
|
+
}
|
|
195
213
|
const project = response.data.node;
|
|
196
214
|
if (!project) {
|
|
197
215
|
return null;
|
|
@@ -31,6 +31,7 @@ describe('ApiV3CheerioRestIssueRepository', () => {
|
|
|
31
31
|
labels: [],
|
|
32
32
|
assignees: [],
|
|
33
33
|
createdAt: '2024-01-01T00:00:00Z',
|
|
34
|
+
author: 'test-author',
|
|
34
35
|
customFields: [
|
|
35
36
|
{ name: 'nextActionDate', value: '2000-01-01' },
|
|
36
37
|
{ name: 'nextActionHour', value: '1' },
|
|
@@ -63,7 +64,7 @@ describe('ApiV3CheerioRestIssueRepository', () => {
|
|
|
63
64
|
isInProgress: false,
|
|
64
65
|
isClosed: false,
|
|
65
66
|
createdAt: new Date('2024-01-01T00:00:00Z'),
|
|
66
|
-
author: '',
|
|
67
|
+
author: 'test-author',
|
|
67
68
|
},
|
|
68
69
|
},
|
|
69
70
|
{
|
|
@@ -80,6 +81,7 @@ describe('ApiV3CheerioRestIssueRepository', () => {
|
|
|
80
81
|
labels: [],
|
|
81
82
|
assignees: [],
|
|
82
83
|
createdAt: '2024-01-01T00:00:00Z',
|
|
84
|
+
author: '',
|
|
83
85
|
customFields: [
|
|
84
86
|
{
|
|
85
87
|
name: 'DependedIssueUrls',
|
|
@@ -376,7 +376,7 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
376
376
|
isInProgress: normalizeFieldName(status || '').includes('progress'),
|
|
377
377
|
isClosed: item.state !== 'OPEN',
|
|
378
378
|
createdAt: new Date(item.createdAt || '2000-01-01'),
|
|
379
|
-
author:
|
|
379
|
+
author: item.author,
|
|
380
380
|
};
|
|
381
381
|
};
|
|
382
382
|
getAllIssuesFromCache = async (
|
|
@@ -13,6 +13,7 @@ export type ProjectItem = {
|
|
|
13
13
|
labels: string[];
|
|
14
14
|
assignees: string[];
|
|
15
15
|
createdAt: string;
|
|
16
|
+
author: string;
|
|
16
17
|
customFields: {
|
|
17
18
|
name: string;
|
|
18
19
|
value: string | null;
|
|
@@ -58,7 +59,7 @@ export class GraphqlProjectItemRepository extends BaseGitHubRepository {
|
|
|
58
59
|
},
|
|
59
60
|
})
|
|
60
61
|
.json<{
|
|
61
|
-
data
|
|
62
|
+
data?: {
|
|
62
63
|
repository: {
|
|
63
64
|
issue: {
|
|
64
65
|
projectItems: {
|
|
@@ -70,8 +71,17 @@ export class GraphqlProjectItemRepository extends BaseGitHubRepository {
|
|
|
70
71
|
};
|
|
71
72
|
};
|
|
72
73
|
};
|
|
74
|
+
errors?: { message: string }[];
|
|
73
75
|
}>();
|
|
74
76
|
|
|
77
|
+
if (!response.data) {
|
|
78
|
+
const errorMessages = response.errors
|
|
79
|
+
? response.errors.map((e) => e.message).join('; ')
|
|
80
|
+
: 'no data field in response';
|
|
81
|
+
throw new Error(
|
|
82
|
+
`GitHub GraphQL API returned no data for fetchItemId: ${errorMessages}`,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
75
85
|
const projectItems: {
|
|
76
86
|
id: string;
|
|
77
87
|
project: { id: string };
|
|
@@ -150,6 +160,9 @@ query GetProjectItems($projectId: ID!, $after: String) {
|
|
|
150
160
|
state
|
|
151
161
|
url
|
|
152
162
|
createdAt
|
|
163
|
+
author {
|
|
164
|
+
login
|
|
165
|
+
}
|
|
153
166
|
labels(first: 100) {
|
|
154
167
|
nodes {
|
|
155
168
|
name
|
|
@@ -170,6 +183,9 @@ query GetProjectItems($projectId: ID!, $after: String) {
|
|
|
170
183
|
state
|
|
171
184
|
url
|
|
172
185
|
createdAt
|
|
186
|
+
author {
|
|
187
|
+
login
|
|
188
|
+
}
|
|
173
189
|
labels(first: 100) {
|
|
174
190
|
nodes {
|
|
175
191
|
name
|
|
@@ -222,6 +238,7 @@ query GetProjectItems($projectId: ID!, $after: String) {
|
|
|
222
238
|
state: string;
|
|
223
239
|
url: string;
|
|
224
240
|
createdAt: string;
|
|
241
|
+
author: { login: string } | null;
|
|
225
242
|
labels: { nodes: { name: string }[] };
|
|
226
243
|
assignees: { nodes: { login: string }[] };
|
|
227
244
|
};
|
|
@@ -272,6 +289,7 @@ query GetProjectItems($projectId: ID!, $after: String) {
|
|
|
272
289
|
state: string;
|
|
273
290
|
url: string;
|
|
274
291
|
createdAt: string;
|
|
292
|
+
author: { login: string } | null;
|
|
275
293
|
labels: { nodes: { name: string }[] };
|
|
276
294
|
assignees: { nodes: { login: string }[] };
|
|
277
295
|
};
|
|
@@ -338,6 +356,7 @@ query GetProjectItems($projectId: ID!, $after: String) {
|
|
|
338
356
|
state: string;
|
|
339
357
|
url: string;
|
|
340
358
|
createdAt: string;
|
|
359
|
+
author: { login: string } | null;
|
|
341
360
|
labels: { nodes: { name: string }[] };
|
|
342
361
|
assignees: { nodes: { login: string }[] };
|
|
343
362
|
};
|
|
@@ -357,6 +376,7 @@ query GetProjectItems($projectId: ID!, $after: String) {
|
|
|
357
376
|
labels: item.content.labels?.nodes?.map((l) => l.name) || [],
|
|
358
377
|
assignees: item.content.assignees?.nodes?.map((a) => a.login) || [],
|
|
359
378
|
createdAt: item.content.createdAt || new Date().toISOString(),
|
|
379
|
+
author: item.content.author?.login || '',
|
|
360
380
|
customFields: item.fieldValues.nodes
|
|
361
381
|
.filter((field) => !!field.field)
|
|
362
382
|
.map((field) => {
|
|
@@ -495,7 +515,7 @@ query GetProjectFields($owner: String!, $repository: String!, $issueNumber: Int!
|
|
|
495
515
|
},
|
|
496
516
|
})
|
|
497
517
|
.json<{
|
|
498
|
-
data
|
|
518
|
+
data?: {
|
|
499
519
|
repository: {
|
|
500
520
|
issue: {
|
|
501
521
|
projectItems: {
|
|
@@ -517,8 +537,17 @@ query GetProjectFields($owner: String!, $repository: String!, $issueNumber: Int!
|
|
|
517
537
|
};
|
|
518
538
|
};
|
|
519
539
|
};
|
|
540
|
+
errors?: { message: string }[];
|
|
520
541
|
}>();
|
|
521
542
|
|
|
543
|
+
if (!response.data) {
|
|
544
|
+
const errorMessages = response.errors
|
|
545
|
+
? response.errors.map((e) => e.message).join('; ')
|
|
546
|
+
: 'no data field in response';
|
|
547
|
+
throw new Error(
|
|
548
|
+
`GitHub GraphQL API returned no data for getProjectItemFields: ${errorMessages}`,
|
|
549
|
+
);
|
|
550
|
+
}
|
|
522
551
|
const data = response.data;
|
|
523
552
|
const issueFields: {
|
|
524
553
|
fieldName: string;
|
|
@@ -569,6 +598,9 @@ query GetProjectFields($owner: String!, $repository: String!, $issueNumber: Int!
|
|
|
569
598
|
url
|
|
570
599
|
body
|
|
571
600
|
createdAt
|
|
601
|
+
author {
|
|
602
|
+
login
|
|
603
|
+
}
|
|
572
604
|
labels(first: 100) {
|
|
573
605
|
nodes {
|
|
574
606
|
name
|
|
@@ -651,7 +683,7 @@ query GetProjectFields($owner: String!, $repository: String!, $issueNumber: Int!
|
|
|
651
683
|
},
|
|
652
684
|
})
|
|
653
685
|
.json<{
|
|
654
|
-
data
|
|
686
|
+
data?: {
|
|
655
687
|
repository: {
|
|
656
688
|
issue: {
|
|
657
689
|
number: number;
|
|
@@ -660,6 +692,7 @@ query GetProjectFields($owner: String!, $repository: String!, $issueNumber: Int!
|
|
|
660
692
|
url: string;
|
|
661
693
|
body: string;
|
|
662
694
|
createdAt: string;
|
|
695
|
+
author: { login: string } | null;
|
|
663
696
|
labels: { nodes: { name: string }[] };
|
|
664
697
|
assignees: { nodes: { login: string }[] };
|
|
665
698
|
repository: { nameWithOwner: string };
|
|
@@ -682,7 +715,16 @@ query GetProjectFields($owner: String!, $repository: String!, $issueNumber: Int!
|
|
|
682
715
|
};
|
|
683
716
|
};
|
|
684
717
|
};
|
|
718
|
+
errors?: { message: string }[];
|
|
685
719
|
}>();
|
|
720
|
+
if (!response.data) {
|
|
721
|
+
const errorMessages = response.errors
|
|
722
|
+
? response.errors.map((e) => e.message).join('; ')
|
|
723
|
+
: 'no data field in response';
|
|
724
|
+
throw new Error(
|
|
725
|
+
`GitHub GraphQL API returned no data for fetchProjectItemByUrl: ${errorMessages}`,
|
|
726
|
+
);
|
|
727
|
+
}
|
|
686
728
|
const data = response.data;
|
|
687
729
|
if (!data.repository.issue) {
|
|
688
730
|
return null;
|
|
@@ -717,6 +759,7 @@ query GetProjectFields($owner: String!, $repository: String!, $issueNumber: Int!
|
|
|
717
759
|
assignees:
|
|
718
760
|
data.repository.issue.assignees?.nodes?.map((a) => a.login) || [],
|
|
719
761
|
createdAt: data.repository.issue.createdAt || new Date().toISOString(),
|
|
762
|
+
author: data.repository.issue.author?.login || '',
|
|
720
763
|
customFields: item.fieldValues.nodes
|
|
721
764
|
.filter((field) => !!field.field)
|
|
722
765
|
.map((field) => {
|
|
@@ -2039,25 +2039,27 @@ describe('StartPreparationUseCase', () => {
|
|
|
2039
2039
|
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(2);
|
|
2040
2040
|
});
|
|
2041
2041
|
|
|
2042
|
-
it('should
|
|
2043
|
-
const
|
|
2042
|
+
it('should skip issues with empty author when allowedIssueAuthors is configured (deny-by-default)', async () => {
|
|
2043
|
+
const issueWithEmptyAuthor = createMockIssue({
|
|
2044
2044
|
url: 'https://github.com/user/repo/issues/1',
|
|
2045
|
-
title: '
|
|
2045
|
+
title: 'Issue with empty author',
|
|
2046
2046
|
labels: [],
|
|
2047
2047
|
status: 'Awaiting Workspace',
|
|
2048
2048
|
author: '',
|
|
2049
2049
|
});
|
|
2050
|
-
const
|
|
2050
|
+
const issueWithKnownAuthor = createMockIssue({
|
|
2051
2051
|
url: 'https://github.com/user/repo/issues/2',
|
|
2052
|
-
title: '
|
|
2052
|
+
title: 'Issue with known author',
|
|
2053
2053
|
labels: [],
|
|
2054
2054
|
status: 'Awaiting Workspace',
|
|
2055
2055
|
author: 'user1',
|
|
2056
|
+
number: 2,
|
|
2057
|
+
itemId: 'item-2',
|
|
2056
2058
|
});
|
|
2057
2059
|
|
|
2058
2060
|
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
2059
2061
|
mockIssueRepository.getStoryObjectMap.mockResolvedValue(
|
|
2060
|
-
createMockStoryObjectMap([
|
|
2062
|
+
createMockStoryObjectMap([issueWithEmptyAuthor, issueWithKnownAuthor]),
|
|
2061
2063
|
);
|
|
2062
2064
|
mockLocalCommandRunner.runCommand.mockResolvedValue({
|
|
2063
2065
|
stdout: '',
|
|
@@ -2078,33 +2080,26 @@ describe('StartPreparationUseCase', () => {
|
|
|
2078
2080
|
allowIssueCacheMinutes: 0,
|
|
2079
2081
|
});
|
|
2080
2082
|
|
|
2081
|
-
expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(
|
|
2082
|
-
expect(
|
|
2083
|
+
expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(1);
|
|
2084
|
+
expect(mockIssueRepository.updateStatus.mock.calls[0][1]).toMatchObject({
|
|
2085
|
+
url: 'https://github.com/user/repo/issues/2',
|
|
2086
|
+
});
|
|
2087
|
+
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(1);
|
|
2083
2088
|
});
|
|
2084
2089
|
|
|
2085
|
-
it('should
|
|
2086
|
-
const
|
|
2090
|
+
it('should skip issue with empty author when allowedIssueAuthors is set', async () => {
|
|
2091
|
+
const issueWithEmptyAuthor = createMockIssue({
|
|
2087
2092
|
url: 'https://github.com/user/repo/issues/1',
|
|
2088
|
-
title: 'Issue
|
|
2093
|
+
title: 'Issue with empty author',
|
|
2089
2094
|
labels: [],
|
|
2090
2095
|
status: 'Awaiting Workspace',
|
|
2091
2096
|
author: '',
|
|
2092
2097
|
});
|
|
2093
2098
|
|
|
2094
|
-
const storyObjectMap: StoryObjectMap = new Map();
|
|
2095
|
-
storyObjectMap.set('Default Story', {
|
|
2096
|
-
story: {
|
|
2097
|
-
id: 'story-1',
|
|
2098
|
-
name: 'Default Story',
|
|
2099
|
-
color: 'GRAY',
|
|
2100
|
-
description: '',
|
|
2101
|
-
},
|
|
2102
|
-
storyIssue: null,
|
|
2103
|
-
issues: [issueWithoutAuthor],
|
|
2104
|
-
});
|
|
2105
|
-
|
|
2106
2099
|
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
2107
|
-
mockIssueRepository.getStoryObjectMap.mockResolvedValue(
|
|
2100
|
+
mockIssueRepository.getStoryObjectMap.mockResolvedValue(
|
|
2101
|
+
createMockStoryObjectMap([issueWithEmptyAuthor]),
|
|
2102
|
+
);
|
|
2108
2103
|
mockLocalCommandRunner.runCommand.mockResolvedValue({
|
|
2109
2104
|
stdout: '',
|
|
2110
2105
|
stderr: '',
|
|
@@ -2124,8 +2119,8 @@ describe('StartPreparationUseCase', () => {
|
|
|
2124
2119
|
allowIssueCacheMinutes: 0,
|
|
2125
2120
|
});
|
|
2126
2121
|
|
|
2127
|
-
expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(
|
|
2128
|
-
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(
|
|
2122
|
+
expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(0);
|
|
2123
|
+
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(0);
|
|
2129
2124
|
});
|
|
2130
2125
|
|
|
2131
2126
|
it('should not pass --codexHome when codexHomeCandidates is null', async () => {
|
|
@@ -6,7 +6,6 @@ export declare class GitHubIssueCommentRepository implements IssueCommentReposit
|
|
|
6
6
|
constructor(token: string);
|
|
7
7
|
private parseIssueUrl;
|
|
8
8
|
getCommentsFromIssue(issue: Issue): Promise<Comment[]>;
|
|
9
|
-
private getIssueNodeId;
|
|
10
9
|
createComment(issue: Issue, commentContent: string): Promise<void>;
|
|
11
10
|
}
|
|
12
11
|
//# sourceMappingURL=GitHubIssueCommentRepository.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GitHubIssueCommentRepository.d.ts","sourceRoot":"","sources":["../../../src/adapter/repositories/GitHubIssueCommentRepository.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,iEAAiE,CAAC;AACzG,OAAO,EAAE,KAAK,EAAE,MAAM,6BAA6B,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"GitHubIssueCommentRepository.d.ts","sourceRoot":"","sources":["../../../src/adapter/repositories/GitHubIssueCommentRepository.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,iEAAiE,CAAC;AACzG,OAAO,EAAE,KAAK,EAAE,MAAM,6BAA6B,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAexD,qBAAa,4BAA6B,YAAW,sBAAsB;IAC7D,OAAO,CAAC,QAAQ,CAAC,KAAK;gBAAL,KAAK,EAAE,MAAM;IAE1C,OAAO,CAAC,aAAa;IAoBf,oBAAoB,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IA8CtD,aAAa,CAAC,KAAK,EAAE,KAAK,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAsBzE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GraphqlProjectRepository.d.ts","sourceRoot":"","sources":["../../../src/adapter/repositories/GraphqlProjectRepository.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,4DAA4D,CAAC;AAC/F,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAGrE,qBAAa,wBACX,SAAQ,oBACR,YACE,IAAI,CACF,iBAAiB,EACf,YAAY,GACZ,oBAAoB,GACpB,UAAU,GACV,iBAAiB,GACjB,kBAAkB,CACrB;IAEH,qBAAqB,GACnB,YAAY,MAAM,KACjB;QACD,KAAK,EAAE,MAAM,CAAC;QACd,aAAa,EAAE,MAAM,CAAC;KACvB,CAMC;IACF,cAAc,GACZ,OAAO,MAAM,EACb,eAAe,MAAM,KACpB,OAAO,CAAC,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"GraphqlProjectRepository.d.ts","sourceRoot":"","sources":["../../../src/adapter/repositories/GraphqlProjectRepository.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,4DAA4D,CAAC;AAC/F,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAGrE,qBAAa,wBACX,SAAQ,oBACR,YACE,IAAI,CACF,iBAAiB,EACf,YAAY,GACZ,oBAAoB,GACpB,UAAU,GACV,iBAAiB,GACjB,kBAAkB,CACrB;IAEH,qBAAqB,GACnB,YAAY,MAAM,KACjB;QACD,KAAK,EAAE,MAAM,CAAC;QACd,aAAa,EAAE,MAAM,CAAC;KACvB,CAMC;IACF,cAAc,GACZ,OAAO,MAAM,EACb,eAAe,MAAM,KACpB,OAAO,CAAC,MAAM,CAAC,CA8DhB;IACF,kBAAkB,GAChB,YAAY,MAAM,KACjB,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAG9B;IACF,UAAU,GAAU,WAAW,OAAO,CAAC,IAAI,CAAC,KAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CA2NpE;IACF,QAAQ,GAAU,KAAK,MAAM,KAAG,OAAO,CAAC,OAAO,CAAC,CAU9C;IACF,eAAe,GACb,SAAS,OAAO,EAChB,cAAc,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,GAAG;QACvC,EAAE,EAAE,WAAW,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;KAC9B,CAAC,EAAE,KACH,OAAO,CAAC,WAAW,EAAE,CAAC,CA+CvB;IACF,gBAAgB,GACd,SAAS,OAAO,EAChB,eAAe,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,GAAG;QACxC,EAAE,EAAE,WAAW,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;KAC9B,CAAC,EAAE,KACH,OAAO,CAAC,WAAW,EAAE,CAAC,CA4CvB;CACH"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GraphqlProjectItemRepository.d.ts","sourceRoot":"","sources":["../../../../src/adapter/repositories/issue/GraphqlProjectItemRepository.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EAAE,OAAO,EAAE,MAAM,kCAAkC,CAAC;AAC3D,OAAO,EAAE,KAAK,EAAE,MAAM,gCAAgC,CAAC;AACvD,MAAM,MAAM,WAAW,GAAG;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;KACtB,EAAE,CAAC;CACL,CAAC;AACF,eAAO,MAAM,mBAAmB,OAAO,CAAC;AACxC,qBAAa,4BAA6B,SAAQ,oBAAoB;IACpE,WAAW,GACT,WAAW,MAAM,EACjB,OAAO,MAAM,EACb,gBAAgB,MAAM,EACtB,aAAa,MAAM,KAClB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,
|
|
1
|
+
{"version":3,"file":"GraphqlProjectItemRepository.d.ts","sourceRoot":"","sources":["../../../../src/adapter/repositories/issue/GraphqlProjectItemRepository.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EAAE,OAAO,EAAE,MAAM,kCAAkC,CAAC;AAC3D,OAAO,EAAE,KAAK,EAAE,MAAM,gCAAgC,CAAC;AACvD,MAAM,MAAM,WAAW,GAAG;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;KACtB,EAAE,CAAC;CACL,CAAC;AACF,eAAO,MAAM,mBAAmB,OAAO,CAAC;AACxC,qBAAa,4BAA6B,SAAQ,oBAAoB;IACpE,WAAW,GACT,WAAW,MAAM,EACjB,OAAO,MAAM,EACb,gBAAgB,MAAM,EACtB,aAAa,MAAM,KAClB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAoE5B;IACF,iBAAiB,GAAU,WAAW,MAAM,KAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CA8TnE;IACF,gCAAgC,GAC9B,UAAU,MAAM,KACf,OAAO,CACR;QACE,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;KACpB,EAAE,CACJ,CAGC;IAEF,oBAAoB,GAClB,OAAO,MAAM,EACb,gBAAgB,MAAM,EACtB,aAAa,MAAM,KAClB,OAAO,CACR;QACE,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;KACpB,EAAE,CACJ,CAqJC;IACF,qBAAqB,GACnB,UAAU,MAAM,KACf,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CA2L5B;IACF,iBAAiB,GAAI,OAAO,MAAM,KAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAQ/D;IAEF,kBAAkB,GAChB,WAAW,MAAM,EACjB,SAAS,MAAM,EACf,QAAQ,MAAM,EACd,OACI;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,GAChB;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,GAClB;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,GAChB;QAAE,oBAAoB,EAAE,MAAM,CAAA;KAAE,KACnC,OAAO,CAAC,IAAI,CAAC,CAgCd;IAEF,iBAAiB,GACf,WAAW,MAAM,EACjB,SAAS,MAAM,EACf,QAAQ,MAAM,KACb,OAAO,CAAC,IAAI,CAAC,CA+Bd;IACF,sBAAsB,GACpB,SAAS,OAAO,CAAC,IAAI,CAAC,EACtB,SAAS,MAAM,EACf,OAAO,KAAK,CAAC,QAAQ,CAAC,EACtB,MAAM,MAAM,KACX,OAAO,CAAC,IAAI,CAAC,CAEd;IAEF,qBAAqB,GACnB,WAAW,MAAM,EACjB,QAAQ,MAAM,KACb,OAAO,CAAC,IAAI,CAAC,CAgCd;IAEF,+BAA+B,GAC7B,UAAU,MAAM,EAChB,WAAW,MAAM,KAChB,OAAO,CAAC,IAAI,CAAC,CASd;CACH"}
|