feat: react to no-reply decisions for issue comments (#43)
This commit is contained in:
parent
0fb68c886b
commit
b14bbe5d0a
|
|
@ -390,6 +390,19 @@ class IssueAssistantApp {
|
|||
}
|
||||
|
||||
if (!selectedResult.shouldReply) {
|
||||
final noReplyReactionTarget = _latestHumanComment(project, thread);
|
||||
if (noReplyReactionTarget != null) {
|
||||
final reacted = await issueTrackerClient.createNoReplyReaction(
|
||||
provider: project.provider,
|
||||
trackerProject: project.repo,
|
||||
commentId: noReplyReactionTarget.id,
|
||||
);
|
||||
_log(
|
||||
'issue=${project.key}#${issue.number} '
|
||||
'no_reply_reaction=${reacted ? "posted" : "unsupported"} '
|
||||
'comment_id=${noReplyReactionTarget.id}',
|
||||
);
|
||||
}
|
||||
await database.upsertIssueThread(
|
||||
projectKey: project.key,
|
||||
issueNumber: issue.number,
|
||||
|
|
@ -564,6 +577,20 @@ class IssueAssistantApp {
|
|||
return _looksLikeBot(project, latestAuthor, latestText);
|
||||
}
|
||||
|
||||
GitHubIssueComment? _latestHumanComment(
|
||||
ProjectConfig project,
|
||||
IssueThread thread,
|
||||
) {
|
||||
final latestComment = thread.latestComment;
|
||||
if (latestComment == null) {
|
||||
return null;
|
||||
}
|
||||
if (_looksLikeBot(project, latestComment.userLogin, latestComment.body)) {
|
||||
return null;
|
||||
}
|
||||
return latestComment;
|
||||
}
|
||||
|
||||
int _countResponsesSinceLatestHuman(
|
||||
ProjectConfig project,
|
||||
IssueThread thread,
|
||||
|
|
|
|||
|
|
@ -198,6 +198,36 @@ class IssueTrackerClient {
|
|||
);
|
||||
}
|
||||
|
||||
Future<bool> createNoReplyReaction({
|
||||
required IssueTrackerProvider provider,
|
||||
required String trackerProject,
|
||||
required int commentId,
|
||||
}) async {
|
||||
switch (provider) {
|
||||
case IssueTrackerProvider.github:
|
||||
await _runGhJson([
|
||||
'api',
|
||||
'-X',
|
||||
'POST',
|
||||
_buildApiPath(parseRepositorySlug(trackerProject), [
|
||||
'issues',
|
||||
'comments',
|
||||
'$commentId',
|
||||
'reactions',
|
||||
]),
|
||||
'-H',
|
||||
'Accept: application/vnd.github+json',
|
||||
'-f',
|
||||
'content=eyes',
|
||||
]);
|
||||
return true;
|
||||
case IssueTrackerProvider.gitlab:
|
||||
case IssueTrackerProvider.gitea:
|
||||
case IssueTrackerProvider.openproject:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> getAuthenticatedLogin({
|
||||
required IssueTrackerProvider provider,
|
||||
}) {
|
||||
|
|
|
|||
|
|
@ -247,6 +247,7 @@ projects:
|
|||
// And GitHub issue data containing a comment with an explicit assistant mention.
|
||||
final ghScript = File(p.join(sandbox.path, 'gh'));
|
||||
final ghLog = File(p.join(sandbox.path, 'gh-log.jsonl'));
|
||||
final reactionBody = File(p.join(sandbox.path, 'reaction-body.txt'));
|
||||
final issueData = [
|
||||
{
|
||||
'number': 43,
|
||||
|
|
@ -274,6 +275,7 @@ projects:
|
|||
issueListResponse: issueData,
|
||||
commentResponses: {43: commentsData},
|
||||
ghLog: ghLog,
|
||||
reactionBodyFile: reactionBody,
|
||||
);
|
||||
|
||||
// And a responder that explicitly returns no_reply.
|
||||
|
|
@ -327,6 +329,17 @@ projects:
|
|||
.length,
|
||||
1,
|
||||
);
|
||||
expect(
|
||||
logLines
|
||||
.where(
|
||||
(line) => line.contains(
|
||||
'repos/owner/sample/issues/comments/430/reactions',
|
||||
),
|
||||
)
|
||||
.length,
|
||||
1,
|
||||
);
|
||||
expect(await reactionBody.readAsString(), 'eyes');
|
||||
expect(thread.lastCommentId, isNull);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ Future<void> writeFakeGhScript({
|
|||
Set<int>? postCommentIssueNumbers,
|
||||
Map<String, Set<int>>? postCommentIssueNumbersByRepo,
|
||||
File? postedBodyFile,
|
||||
File? reactionBodyFile,
|
||||
String? viewerLogin,
|
||||
bool failViewerLookup = false,
|
||||
}) async {
|
||||
|
|
@ -173,6 +174,41 @@ Future<void> writeFakeGhScript({
|
|||
}
|
||||
}
|
||||
|
||||
for (final repoEntry in normalizedCommentResponsesByRepo.entries) {
|
||||
final repo = repoEntry.key;
|
||||
for (final issueEntry in repoEntry.value.entries) {
|
||||
for (final comment in issueEntry.value) {
|
||||
final commentId = comment['id'];
|
||||
if (commentId is! int) {
|
||||
continue;
|
||||
}
|
||||
buffer
|
||||
..writeln(
|
||||
'if [[ "\$1" == "api" && "\$4" == '
|
||||
'"repos/$repo/issues/comments/$commentId/reactions" ]]; then',
|
||||
)
|
||||
..writeln(r''' for arg in "$@"; do''')
|
||||
..writeln(r''' :''');
|
||||
if (reactionBodyFile != null) {
|
||||
buffer
|
||||
..writeln(r''' if [[ "$arg" == content=* ]]; then''')
|
||||
..writeln(
|
||||
r''' printf '%s' "${arg#content=}" > '''
|
||||
'"${bashScriptPath(reactionBodyFile.path)}"',
|
||||
)
|
||||
..writeln(r''' fi''');
|
||||
}
|
||||
buffer
|
||||
..writeln(r''' done''')
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(jsonEncode({'id': 990, 'content': 'eyes'}))
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buffer
|
||||
..writeln(r'''echo "unexpected gh args: $*" >&2''')
|
||||
..writeln('exit 1');
|
||||
|
|
|
|||
Loading…
Reference in New Issue