Skip to content

Commit e8e7ba2

Browse files
authored
feat: trigger CodeRabbit plan after issue assignment (#2577)
Signed-off-by: ParasSalonia <parassalonia22@gmail.com> Signed-off-by: Paras Salonia <parassalonia22@gmail.com>
1 parent a9cc775 commit e8e7ba2

4 files changed

Lines changed: 131 additions & 37 deletions

File tree

.github/scripts/__tests__/jest/issue-assign.test.js

Lines changed: 71 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ jest.mock('../../shared/api/github-api', () => ({
1313
assignIssue: jest.fn(),
1414
}));
1515

16+
jest.mock('../../shared/helpers/coderabbit-plan.js', () => ({
17+
triggerCodeRabbitPlan: jest.fn(),
18+
hasExistingCodeRabbitPlan: jest.fn(),
19+
}));
20+
1621
jest.mock('../../shared/helpers/comment', () => ({
1722
buildAlreadyAssignedComment: jest.fn(() => 'already assigned'),
1823
buildGuardComment: jest.fn(() => 'guard comment'),
@@ -35,6 +40,11 @@ const { runAssignmentFlow } = require('../../shared/core/issue-assign');
3540
const githubApi = require('../../shared/api/github-api');
3641
const spam = require('../../shared/helpers/spam');
3742

43+
const {
44+
triggerCodeRabbitPlan,
45+
hasExistingCodeRabbitPlan,
46+
} = require('../../shared/helpers/coderabbit-plan.js');
47+
3848
function createContext(overrides = {}) {
3949
return {
4050
payload: {
@@ -86,6 +96,9 @@ beforeEach(() => {
8696
spam.isSpamBlockedLevel.mockReturnValue(false);
8797
spam.isSpamLimited.mockReturnValue(false);
8898
spam.getAssignmentLimit.mockReturnValue(5);
99+
100+
hasExistingCodeRabbitPlan.mockResolvedValue(false);
101+
triggerCodeRabbitPlan.mockResolvedValue(true);
89102
});
90103

91104
describe('runAssignmentFlow - validation', () => {
@@ -597,8 +610,9 @@ describe('runAssignmentFlow - assignment', () => {
597610
expect(githubApi.assignIssue).not.toHaveBeenCalled();
598611
});
599612

600-
test('assigns issue when all checks pass', async () => {
613+
test('assigns issue and triggers CodeRabbit when all checks pass', async () => {
601614
githubApi.getOpenAssignments.mockResolvedValue(0);
615+
hasExistingCodeRabbitPlan.mockResolvedValue(false);
602616

603617
const github = createGithub();
604618
const context = createContext();
@@ -613,8 +627,54 @@ describe('runAssignmentFlow - assignment', () => {
613627
username: 'parv',
614628
});
615629

630+
expect(hasExistingCodeRabbitPlan).toHaveBeenCalledWith(
631+
github,
632+
'hiero-ledger',
633+
'hiero-sdk-python',
634+
10
635+
);
636+
637+
expect(triggerCodeRabbitPlan).toHaveBeenCalledWith(
638+
github,
639+
'hiero-ledger',
640+
'hiero-sdk-python',
641+
context.payload.issue
642+
);
643+
616644
expect(githubApi.postIssueComment).not.toHaveBeenCalled();
617645
});
646+
647+
test('does not trigger CodeRabbit when a plan already exists', async () => {
648+
githubApi.getOpenAssignments.mockResolvedValue(0);
649+
hasExistingCodeRabbitPlan.mockResolvedValue(true);
650+
651+
const github = createGithub();
652+
const context = createContext();
653+
654+
await runAssignmentFlow({ github, context });
655+
656+
expect(githubApi.assignIssue).toHaveBeenCalled();
657+
expect(hasExistingCodeRabbitPlan).toHaveBeenCalled();
658+
expect(triggerCodeRabbitPlan).not.toHaveBeenCalled();
659+
});
660+
661+
test('does not fail assignment when CodeRabbit trigger fails', async () => {
662+
githubApi.getOpenAssignments.mockResolvedValue(0);
663+
hasExistingCodeRabbitPlan.mockResolvedValue(false);
664+
triggerCodeRabbitPlan.mockRejectedValue(
665+
new Error('CodeRabbit API failed')
666+
);
667+
668+
const github = createGithub();
669+
const context = createContext();
670+
671+
await expect(
672+
runAssignmentFlow({ github, context })
673+
).resolves.not.toThrow();
674+
675+
expect(githubApi.assignIssue).toHaveBeenCalled();
676+
expect(triggerCodeRabbitPlan).toHaveBeenCalled();
677+
});
618678
});
619679

620680
describe('runAssignmentFlow - error handling', () => {
@@ -676,31 +736,21 @@ describe('runAssignmentFlow - error handling', () => {
676736
expect(githubApi.assignIssue).toHaveBeenCalled();
677737
});
678738

679-
test('propagates post comment errors', async () => {
680-
githubApi.postIssueComment.mockRejectedValue(
681-
new Error('Comment failed')
739+
test('does not trigger CodeRabbit when assignment fails', async () => {
740+
githubApi.assignIssue.mockRejectedValue(
741+
new Error('Assignment failed')
682742
);
683743

684-
githubApi.countCompletedIssuesWithLabel.mockResolvedValue(0);
685-
686744
const github = createGithub();
745+
const context = createContext();
687746

688-
const context = createContext({
689-
issue: {
690-
number: 42,
691-
assignees: [],
692-
labels: [
693-
{
694-
name: 'skill: intermediate',
695-
},
696-
],
697-
},
747+
await runAssignmentFlow({
748+
github,
749+
context,
698750
});
699751

700-
await expect(
701-
runAssignmentFlow({ github, context })
702-
).rejects.toThrow('Comment failed');
703-
704-
expect(githubApi.assignIssue).not.toHaveBeenCalled();
752+
expect(githubApi.assignIssue).toHaveBeenCalled();
753+
expect(hasExistingCodeRabbitPlan).not.toHaveBeenCalled();
754+
expect(triggerCodeRabbitPlan).not.toHaveBeenCalled();
705755
});
706756
});

.github/scripts/shared/core/issue-assign.js

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ const {
5252
getAssignmentLimit,
5353
} = require('../helpers/spam.js');
5454

55+
const {
56+
triggerCodeRabbitPlan,
57+
hasExistingCodeRabbitPlan,
58+
} = require('../helpers/coderabbit-plan.js');
59+
5560
/**
5661
* Returns true if a comment contains the `/assign` command.
5762
*
@@ -281,14 +286,46 @@ async function runAssignmentFlow({ github, context }) {
281286
username: commenter,
282287
});
283288
} catch (error) {
284-
console.error("[assign-bot] Failed to assign issue:", {
289+
console.error('[assign-bot] Failed to assign issue:', {
285290
message: error.message,
286291
});
292+
return;
287293
}
288-
return;
289294

290-
}
295+
// Trigger CodeRabbit after successful assignment
296+
try {
297+
const planExists = await hasExistingCodeRabbitPlan(
298+
github,
299+
owner,
300+
repoName,
301+
issueNumber
302+
);
291303

304+
if (planExists === false) {
305+
await triggerCodeRabbitPlan(
306+
github,
307+
owner,
308+
repoName,
309+
issue
310+
);
311+
} else if (planExists === true) {
312+
console.log(
313+
`[assign-bot] CodeRabbit plan already exists for #${issueNumber}`
314+
);
315+
} else {
316+
console.log(
317+
`[assign-bot] Unable to determine whether a CodeRabbit plan exists for #${issueNumber}. Skipping trigger.`
318+
);
319+
}
320+
} catch (error) {
321+
console.error('[assign-bot] CodeRabbit plan trigger failed:', {
322+
message: error.message,
323+
issueNumber,
324+
});
325+
}
326+
327+
return;
328+
}
292329
module.exports = {
293330
runAssignmentFlow,
294331
};

.github/scripts/coderabbit_plan_trigger.js renamed to .github/scripts/shared/helpers/coderabbit-plan.js

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Script to trigger CodeRabbit plan for all difficulty-labeled issues (including GFI)
22

33
const CODERABBIT_MARKER = '<!-- CodeRabbit Plan Trigger -->';
4-
const { DIFFICULTY_LABELS } = require('./shared/labels.js');
4+
const { DIFFICULTY_LABELS } = require('../labels.js');
55

66
async function triggerCodeRabbitPlan(github, owner, repo, issue, marker = CODERABBIT_MARKER) {
77
const comment = `${marker} @coderabbitai plan`;
@@ -73,7 +73,7 @@ async function hasExistingCodeRabbitPlan(github, owner, repo, issueNumber) {
7373
issueNumber,
7474
});
7575
// Return false to allow plan trigger attempt (fail-open for better UX)
76-
return false;
76+
return null;
7777
}
7878
}
7979

@@ -110,10 +110,18 @@ function logSummary(owner, repo, issue) {
110110
}
111111

112112
// Main workflow handler (default export for workflow usage)
113-
async function main({ github, context }) {
113+
async function triggerCodeRabbitPlanForIssue({ github, context }) {
114114
try {
115115
const { owner, repo } = context.repo;
116-
const { issue: eventIssue, label } = context.payload;
116+
117+
const payload = context?.payload;
118+
119+
if (!payload) {
120+
console.log('No event payload');
121+
return;
122+
}
123+
124+
const { issue: eventIssue, label } = payload;
117125

118126
// Validations
119127
if (!eventIssue?.number) return console.log('No issue in payload');
@@ -149,10 +157,9 @@ async function main({ github, context }) {
149157
}
150158
}
151159

152-
// Default export for workflow usage: await script({ github, context })
153-
module.exports = main;
154-
155-
// Named exports for reuse by other scripts (e.g., GFI assignment bot)
156-
module.exports.triggerCodeRabbitPlan = triggerCodeRabbitPlan;
157-
module.exports.hasExistingCodeRabbitPlan = hasExistingCodeRabbitPlan;
158-
module.exports.CODERABBIT_MARKER = CODERABBIT_MARKER;
160+
module.exports = {
161+
triggerCodeRabbitPlan,
162+
hasExistingCodeRabbitPlan,
163+
CODERABBIT_MARKER,
164+
triggerCodeRabbitPlanForIssue,
165+
};

.github/workflows/approved-issues.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,5 +63,5 @@ jobs:
6363
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
6464
with:
6565
script: |
66-
const script = require('./.github/scripts/coderabbit_plan_trigger.js');
67-
await script({ github, context });
66+
const { triggerCodeRabbitPlanForIssue } = require('./.github/scripts/shared/helpers/coderabbit-plan.js');
67+
await triggerCodeRabbitPlanForIssue({ github, context });

0 commit comments

Comments
 (0)