Skip to content

Commit 46534e6

Browse files
authored
feat(aft): Adds review for generating code review requests text (#4798)
1 parent 091df2a commit 46534e6

File tree

2 files changed

+152
-1
lines changed

2 files changed

+152
-1
lines changed

packages/aft/lib/src/command_runner.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// SPDX-License-Identifier: Apache-2.0
33

44
import 'package:aft/aft.dart';
5+
import 'package:aft/src/commands/review_command.dart';
56
import 'package:aft/src/commands/save_repo_state_command.dart';
67
import 'package:args/command_runner.dart';
78

@@ -32,7 +33,8 @@ Future<void> run(List<String> args) async {
3233
..addCommand(SaveRepoStateCommand())
3334
..addCommand(RunCommand())
3435
..addCommand(DocsCommand())
35-
..addCommand(ServeCommand());
36+
..addCommand(ServeCommand())
37+
..addCommand(ReviewCommand());
3638

3739
try {
3840
final argResults = runner.argParser.parse(args);
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import 'dart:convert';
5+
import 'dart:io';
6+
7+
import 'package:aft/aft.dart';
8+
9+
class GitHubData {
10+
GitHubData.fromJson(Map<String, dynamic> json)
11+
: title = json['title'] as String,
12+
body = json['body'] as String,
13+
number = json['number'] as int,
14+
changedFiles = json['changedFiles'] as int,
15+
additions = json['additions'] as int,
16+
deletions = json['deletions'] as int,
17+
url = json['url'] as String;
18+
19+
String title;
20+
String body;
21+
int number;
22+
int changedFiles;
23+
int additions;
24+
int deletions;
25+
String url;
26+
27+
final String _legal =
28+
'By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.';
29+
final String _lineBreak = '========================================';
30+
31+
final emojis = {
32+
ReviewPriority.urgent: ':alert:',
33+
ReviewPriority.high: ':arrow_double_up:',
34+
ReviewPriority.normal: ':arrow_up:',
35+
ReviewPriority.low: ':arrow_down:',
36+
};
37+
38+
@override
39+
String toString() {
40+
return '{Title: $title,\nBody: $body,\nNumber: $number,\nChanged Files: $changedFiles,\nAdditions: $additions,\nDeletions: $deletions}';
41+
}
42+
43+
/// Converts the PR data to a Slack-formatted string.
44+
String toMarkdown(ReviewPriority priority) {
45+
return '''
46+
$_lineBreak
47+
${emojis[priority]} *$title - [$number]($url)*
48+
> +$additions -$deletions | files: $changedFiles
49+
${body.replaceAll(_legal, '').trim()}
50+
*Reviewers:*
51+
$_lineBreak''';
52+
}
53+
}
54+
55+
enum ReviewPriority { urgent, high, normal, low }
56+
57+
/// Command to clean all temporary files in the repo.
58+
class ReviewCommand extends AmplifyCommand {
59+
ReviewCommand() {
60+
argParser
61+
..addOption(
62+
'ref',
63+
abbr: 'r',
64+
help:
65+
'The PR reference. Can be a PR number or URL. Defaults to the current branch.',
66+
)
67+
..addOption(
68+
'priority',
69+
abbr: 'p',
70+
help: 'Determines the emoji to use for the PR review.',
71+
allowed: ReviewPriority.values.map((e) => e.name).toList(),
72+
defaultsTo: ReviewPriority.normal.name,
73+
);
74+
}
75+
@override
76+
String get description => 'Creates PR review text for slack';
77+
78+
@override
79+
String get name => 'review';
80+
81+
/// The PR ref to review.
82+
late final ref = argResults?['ref'] as String? ?? '';
83+
84+
/// Determines the emoji to use for the PR review.
85+
late final priority = ReviewPriority.values.firstWhere(
86+
(e) => e.name == argResults!['priority'] as String,
87+
orElse: () => ReviewPriority.normal,
88+
);
89+
90+
/// Verifies that the PR is ready using gh CLI.
91+
Future<void> _verifyPR(String ref) async {
92+
final branchName = await Process.run(
93+
'gh',
94+
['pr', 'ready', ref],
95+
);
96+
if (branchName.exitCode != 0) {
97+
final error = branchName.stderr.toString();
98+
if (error.contains('Could not resolve to a PullRequest')) {
99+
logger.error('A PR is not ready at ref: $ref');
100+
} else {
101+
logger.error('Invalid ref: $error');
102+
}
103+
exit(1);
104+
}
105+
}
106+
107+
/// Fetches PR data from GitHub using gh CLI.
108+
Future<GitHubData> _getGitHubData(String ref) async {
109+
final process = await Process.run(
110+
'gh',
111+
[
112+
'pr',
113+
'view',
114+
ref,
115+
'--json',
116+
'title,body,number,changedFiles,additions,deletions,url',
117+
],
118+
);
119+
if (process.exitCode != 0) {
120+
logger.error('Could not fetch PR data from GitHub');
121+
exit(1);
122+
}
123+
if (process.stdout is! String) {
124+
logger.error('GitHub data is not a string');
125+
exit(1);
126+
}
127+
128+
final prDataJson =
129+
jsonDecode(process.stdout as String) as Map<String, dynamic>;
130+
final prData = GitHubData.fromJson(prDataJson);
131+
return prData;
132+
}
133+
134+
@override
135+
Future<void> run() async {
136+
await super.run();
137+
138+
await _verifyPR(ref);
139+
140+
final ghData = await _getGitHubData(ref);
141+
142+
final slackString = ghData.toMarkdown(priority);
143+
144+
logger
145+
..info(slackString)
146+
..info('After pasting into slack, press CMD+SHIFT+F to format the text.')
147+
..info('Review output was successful!');
148+
}
149+
}

0 commit comments

Comments
 (0)