Skip to content

Commit 3a1e06b

Browse files
Release v3.1.0
- Add PR comment minimization functionality with GraphQL - Fix lint errors and improve test coverage - Update release script for automatic GitHub releases - Update changelog and version
1 parent 47282f7 commit 3a1e06b

File tree

8 files changed

+344
-8
lines changed

8 files changed

+344
-8
lines changed

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,21 @@
22

33
# Changelog
44

5+
## [3.1.0] - 2025-12-30
6+
7+
### Added
8+
9+
- Added PR comment minimization functionality to hide previous bot comments before creating new ones
10+
- Added comprehensive GraphQL support for PR comment management with pagination
11+
- Enhanced release script to automatically create GitHub releases with auto-generated notes
12+
- Added repository detection from git remote URL in release script
13+
14+
### Fixed
15+
16+
- Fixed lint errors by allowing 'any' types in test files
17+
- Added comprehensive test coverage for edge cases including pagination failures and API errors
18+
- Improved error handling in PR comment operations
19+
520
## [3.0.1] - 2025-12-30
621

722
### Fixed

__tests__/main.test.ts

Lines changed: 135 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ await jest.unstable_mockModule('@actions/github', async () => {
5858
payload: {},
5959
repo: { owner: 'test-owner', repo: 'test-repo' },
6060
},
61-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
6261
} as any;
6362
});
6463

@@ -83,9 +82,11 @@ let setOutputMock: jest.Mock;
8382
let mockOctokit: {
8483
rest: {
8584
issues: {
86-
createComment: jest.Mock;
85+
createComment: jest.Mock<any>;
86+
listComments: jest.Mock<any>;
8787
};
8888
};
89+
graphql: jest.Mock<any>;
8990
};
9091

9192
describe('action', () => {
@@ -140,8 +141,10 @@ describe('action', () => {
140141
rest: {
141142
issues: {
142143
createComment: jest.fn(),
144+
listComments: jest.fn(),
143145
},
144146
},
147+
graphql: jest.fn(),
145148
};
146149
githubMocks.getOctokit.mockReturnValue(mockOctokit);
147150
});
@@ -406,9 +409,41 @@ at Tests.Registration.main(Registration.java:202)`,
406409
};
407410
testInputs.reports = ['junit|fixtures/junit-many-errors.xml'];
408411
testInputs['max-annotations'] = '2';
409-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
410-
(mockOctokit.rest.issues.createComment as any).mockResolvedValue({});
412+
// Mock listComments to return some previous bot comments
413+
mockOctokit.rest.issues.listComments.mockResolvedValue({
414+
data: [
415+
{
416+
id: 1,
417+
node_id: 'comment1',
418+
body: '## Skipped Annotations\n\nOld comment',
419+
},
420+
{
421+
id: 2,
422+
node_id: 'comment2',
423+
body: 'Some other comment',
424+
},
425+
],
426+
});
427+
// Mock graphql for minimizing comments
428+
mockOctokit.graphql.mockResolvedValue({});
429+
mockOctokit.rest.issues.createComment.mockResolvedValue({});
411430
await main.run();
431+
expect(mockOctokit.rest.issues.listComments).toHaveBeenCalledWith({
432+
owner: 'test-owner',
433+
repo: 'test-repo',
434+
issue_number: 123,
435+
page: 1,
436+
per_page: 100,
437+
});
438+
expect(mockOctokit.graphql).toHaveBeenCalledWith(
439+
expect.stringContaining('MinimizeComment'),
440+
{
441+
input: {
442+
subjectId: 'comment1',
443+
classifier: 'OUTDATED',
444+
},
445+
},
446+
);
412447
expect(mockOctokit.rest.issues.createComment).toHaveBeenCalledWith({
413448
owner: 'test-owner',
414449
repo: 'test-repo',
@@ -427,15 +462,109 @@ at Tests.Registration.main(Registration.java:202)`,
427462
};
428463
testInputs.reports = ['junit|fixtures/junit-many-errors.xml'];
429464
testInputs['max-annotations'] = '2';
465+
// Mock listComments
466+
mockOctokit.rest.issues.listComments.mockResolvedValue({
467+
data: [],
468+
});
469+
// Mock graphql
470+
mockOctokit.graphql.mockResolvedValue({});
430471
const apiError = new Error('API Error');
431-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
432-
(mockOctokit.rest.issues.createComment as any).mockRejectedValue(apiError);
472+
mockOctokit.rest.issues.createComment.mockRejectedValue(apiError);
433473
await main.run();
434474
expect(errorMock).toHaveBeenCalledWith(
435475
`Failed to create PR comment: ${apiError}`,
436476
);
437477
});
438478

479+
it('should handle pagination when fetching comments', async () => {
480+
// Mock GitHub context to be on a PR
481+
(github.context as MutableContext).payload = {
482+
pull_request: { number: 123 },
483+
};
484+
testInputs.reports = ['junit|fixtures/junit-many-errors.xml'];
485+
testInputs['max-annotations'] = '2';
486+
// Mock listComments to return full page on first call, then empty page
487+
mockOctokit.rest.issues.listComments
488+
.mockResolvedValueOnce({
489+
data: Array(100)
490+
.fill(null)
491+
.map((_, i) => ({
492+
id: i + 1,
493+
node_id: `comment${i + 1}`,
494+
body: '## Skipped Annotations\n\nOld comment',
495+
})),
496+
})
497+
.mockResolvedValueOnce({
498+
data: [],
499+
});
500+
// Mock graphql for minimizing comments
501+
mockOctokit.graphql.mockResolvedValue({});
502+
mockOctokit.rest.issues.createComment.mockResolvedValue({});
503+
await main.run();
504+
expect(mockOctokit.rest.issues.listComments).toHaveBeenCalledTimes(2);
505+
expect(mockOctokit.rest.issues.listComments).toHaveBeenCalledWith({
506+
owner: 'test-owner',
507+
repo: 'test-repo',
508+
issue_number: 123,
509+
page: 1,
510+
per_page: 100,
511+
});
512+
expect(mockOctokit.rest.issues.listComments).toHaveBeenCalledWith({
513+
owner: 'test-owner',
514+
repo: 'test-repo',
515+
issue_number: 123,
516+
page: 2,
517+
per_page: 100,
518+
});
519+
expect(mockOctokit.graphql).toHaveBeenCalledTimes(100);
520+
});
521+
522+
it('should handle GraphQL minimization failure for individual comments', async () => {
523+
// Mock GitHub context to be on a PR
524+
(github.context as MutableContext).payload = {
525+
pull_request: { number: 123 },
526+
};
527+
testInputs.reports = ['junit|fixtures/junit-many-errors.xml'];
528+
testInputs['max-annotations'] = '2';
529+
// Mock listComments to return bot comments
530+
mockOctokit.rest.issues.listComments.mockResolvedValue({
531+
data: [
532+
{
533+
id: 1,
534+
node_id: 'comment1',
535+
body: '## Skipped Annotations\n\nOld comment',
536+
},
537+
],
538+
});
539+
// Mock graphql to fail for minimization
540+
const graphqlError = new Error('GraphQL Error');
541+
mockOctokit.graphql.mockRejectedValue(graphqlError);
542+
mockOctokit.rest.issues.createComment.mockResolvedValue({});
543+
await main.run();
544+
expect(warningMock).toHaveBeenCalledWith(
545+
'Failed to minimize comment 1: Error: GraphQL Error',
546+
);
547+
expect(mockOctokit.rest.issues.createComment).toHaveBeenCalled();
548+
});
549+
550+
it('should handle listComments API failure', async () => {
551+
// Mock GitHub context to be on a PR
552+
(github.context as MutableContext).payload = {
553+
pull_request: { number: 123 },
554+
};
555+
testInputs.reports = ['junit|fixtures/junit-many-errors.xml'];
556+
testInputs['max-annotations'] = '2';
557+
// Mock listComments to fail
558+
const apiError = new Error('API Error');
559+
mockOctokit.rest.issues.listComments.mockRejectedValue(apiError);
560+
mockOctokit.rest.issues.createComment.mockResolvedValue({});
561+
await main.run();
562+
expect(warningMock).toHaveBeenCalledWith(
563+
'Failed to minimize previous bot comments: Error: API Error',
564+
);
565+
expect(mockOctokit.rest.issues.createComment).toHaveBeenCalled();
566+
});
567+
439568
it('should throw error for invalid report format', async () => {
440569
testInputs.reports = ['invalid-format-no-pipe'];
441570
await expect(main.run()).rejects.toThrow(

dist/index.js

Lines changed: 57 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/index.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

eslint.config.mjs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,4 +82,10 @@ export default [
8282
'prettier/prettier': 'error',
8383
},
8484
},
85+
{
86+
files: ['__tests__/**/*.ts'],
87+
rules: {
88+
'@typescript-eslint/no-explicit-any': 'off',
89+
},
90+
},
8591
];

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "report-annotate",
33
"description": "Annotate PR from report e.g. junit",
4-
"version": "3.0.1",
4+
"version": "3.1.0",
55
"author": "",
66
"type": "module",
77
"private": true,

script/release

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ set -e
2424
# 8. Point separate major release tag (e.g. v1, v2) to the new release
2525
# 9. Push the new tags (with commits, if any) to remote
2626
# 10. If this is a major release, create a 'releases/v#' branch and push
27+
# 11. Create a GitHub release with auto-generated release notes
2728
#
2829
# Usage:
2930
#
@@ -129,5 +130,57 @@ if [ $is_major_release = 'yes' ]; then
129130
echo -e "Branch: ${BOLD_GREEN}releases/$new_major_release_tag${OFF} pushed to remote"
130131
fi
131132

133+
# 11. Create a GitHub release
134+
echo -e "Creating GitHub release for ${BOLD_BLUE}$new_tag${OFF}..."
135+
136+
# Get repository information from git remote
137+
if ! remote_url=$(git remote get-url origin 2>/dev/null); then
138+
echo -e "${BOLD_RED}Failed to get git remote URL. Please ensure you're in a git repository with an 'origin' remote.${OFF}"
139+
exit 1
140+
fi
141+
142+
# Extract owner/repo from remote URL
143+
# Handle both HTTPS and SSH formats:
144+
# https://github.com/owner/repo.git -> owner/repo
145+
# git@github.com:owner/repo.git -> owner/repo
146+
if [[ $remote_url =~ github\.com[/:]([^/]+)/([^/.]+) ]]; then
147+
repo_owner="${BASH_REMATCH[1]}"
148+
repo_name="${BASH_REMATCH[2]}"
149+
github_repository="$repo_owner/$repo_name"
150+
echo -e "Detected repository: ${BOLD_BLUE}$github_repository${OFF}"
151+
else
152+
echo -e "${BOLD_RED}Could not parse repository information from git remote URL: $remote_url${OFF}"
153+
echo -e "Expected format: https://github.com/owner/repo or git@github.com:owner/repo"
154+
exit 1
155+
fi
156+
157+
# Check if gh CLI is available
158+
if ! command -v gh &> /dev/null; then
159+
echo -e "${BOLD_RED}GitHub CLI (gh) is not installed. Please install it to create releases automatically.${OFF}"
160+
echo -e "You can manually create the release at: https://github.com/$github_repository/releases/new"
161+
echo -e "Tag: ${BOLD_BLUE}$new_tag${OFF}"
162+
exit 1
163+
fi
164+
165+
# Check if user is authenticated with GitHub CLI
166+
if ! gh auth status &> /dev/null; then
167+
echo -e "${BOLD_RED}Not authenticated with GitHub CLI. Please run 'gh auth login' first.${OFF}"
168+
echo -e "You can manually create the release at: https://github.com/$github_repository/releases/new"
169+
echo -e "Tag: ${BOLD_BLUE}$new_tag${OFF}"
170+
exit 1
171+
fi
172+
173+
# Generate release notes automatically
174+
echo -e "Generating release notes..."
175+
if gh release create "$new_tag" --generate-notes --draft --repo "$github_repository"; then
176+
echo -e "Draft release created: ${BOLD_GREEN}$new_tag${OFF}"
177+
echo -e "Please review and publish the release at: https://github.com/$github_repository/releases"
178+
else
179+
echo -e "${BOLD_RED}Failed to create GitHub release.${OFF}"
180+
echo -e "You can manually create the release at: https://github.com/$github_repository/releases/new"
181+
echo -e "Tag: ${BOLD_BLUE}$new_tag${OFF}"
182+
exit 1
183+
fi
184+
132185
# Completed
133186
echo -e "${BOLD_GREEN}Done!${OFF}"

0 commit comments

Comments
 (0)