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) {
|
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(
|
await database.upsertIssueThread(
|
||||||
projectKey: project.key,
|
projectKey: project.key,
|
||||||
issueNumber: issue.number,
|
issueNumber: issue.number,
|
||||||
|
|
@ -564,6 +577,20 @@ class IssueAssistantApp {
|
||||||
return _looksLikeBot(project, latestAuthor, latestText);
|
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(
|
int _countResponsesSinceLatestHuman(
|
||||||
ProjectConfig project,
|
ProjectConfig project,
|
||||||
IssueThread thread,
|
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({
|
Future<String?> getAuthenticatedLogin({
|
||||||
required IssueTrackerProvider provider,
|
required IssueTrackerProvider provider,
|
||||||
}) {
|
}) {
|
||||||
|
|
|
||||||
|
|
@ -247,6 +247,7 @@ projects:
|
||||||
// And GitHub issue data containing a comment with an explicit assistant mention.
|
// And GitHub issue data containing a comment with an explicit assistant mention.
|
||||||
final ghScript = File(p.join(sandbox.path, 'gh'));
|
final ghScript = File(p.join(sandbox.path, 'gh'));
|
||||||
final ghLog = File(p.join(sandbox.path, 'gh-log.jsonl'));
|
final ghLog = File(p.join(sandbox.path, 'gh-log.jsonl'));
|
||||||
|
final reactionBody = File(p.join(sandbox.path, 'reaction-body.txt'));
|
||||||
final issueData = [
|
final issueData = [
|
||||||
{
|
{
|
||||||
'number': 43,
|
'number': 43,
|
||||||
|
|
@ -274,6 +275,7 @@ projects:
|
||||||
issueListResponse: issueData,
|
issueListResponse: issueData,
|
||||||
commentResponses: {43: commentsData},
|
commentResponses: {43: commentsData},
|
||||||
ghLog: ghLog,
|
ghLog: ghLog,
|
||||||
|
reactionBodyFile: reactionBody,
|
||||||
);
|
);
|
||||||
|
|
||||||
// And a responder that explicitly returns no_reply.
|
// And a responder that explicitly returns no_reply.
|
||||||
|
|
@ -327,6 +329,17 @@ projects:
|
||||||
.length,
|
.length,
|
||||||
1,
|
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);
|
expect(thread.lastCommentId, isNull);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ Future<void> writeFakeGhScript({
|
||||||
Set<int>? postCommentIssueNumbers,
|
Set<int>? postCommentIssueNumbers,
|
||||||
Map<String, Set<int>>? postCommentIssueNumbersByRepo,
|
Map<String, Set<int>>? postCommentIssueNumbersByRepo,
|
||||||
File? postedBodyFile,
|
File? postedBodyFile,
|
||||||
|
File? reactionBodyFile,
|
||||||
String? viewerLogin,
|
String? viewerLogin,
|
||||||
bool failViewerLookup = false,
|
bool failViewerLookup = false,
|
||||||
}) async {
|
}) 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
|
buffer
|
||||||
..writeln(r'''echo "unexpected gh args: $*" >&2''')
|
..writeln(r'''echo "unexpected gh args: $*" >&2''')
|
||||||
..writeln('exit 1');
|
..writeln('exit 1');
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue