Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(aft): Adds review for generating code review requests text #4798

Merged
merged 4 commits into from
May 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion packages/aft/lib/src/command_runner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0

import 'package:aft/aft.dart';
import 'package:aft/src/commands/review_command.dart';
import 'package:aft/src/commands/save_repo_state_command.dart';
import 'package:args/command_runner.dart';

Expand Down Expand Up @@ -32,7 +33,8 @@ Future<void> run(List<String> args) async {
..addCommand(SaveRepoStateCommand())
..addCommand(RunCommand())
..addCommand(DocsCommand())
..addCommand(ServeCommand());
..addCommand(ServeCommand())
..addCommand(ReviewCommand());

try {
final argResults = runner.argParser.parse(args);
Expand Down
149 changes: 149 additions & 0 deletions packages/aft/lib/src/commands/review_command.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import 'dart:convert';
import 'dart:io';

import 'package:aft/aft.dart';

class GitHubData {
GitHubData.fromJson(Map<String, dynamic> json)
: title = json['title'] as String,
body = json['body'] as String,
number = json['number'] as int,
changedFiles = json['changedFiles'] as int,
additions = json['additions'] as int,
deletions = json['deletions'] as int,
url = json['url'] as String;

String title;
String body;
int number;
int changedFiles;
int additions;
int deletions;
String url;

final String _legal =
'By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.';
final String _lineBreak = '========================================';

final emojis = {
ReviewPriority.urgent: ':alert:',
ReviewPriority.high: ':arrow_double_up:',
ReviewPriority.normal: ':arrow_up:',
ReviewPriority.low: ':arrow_down:',
};

@override
String toString() {
return '{Title: $title,\nBody: $body,\nNumber: $number,\nChanged Files: $changedFiles,\nAdditions: $additions,\nDeletions: $deletions}';
}

/// Converts the PR data to a Slack-formatted string.
String toMarkdown(ReviewPriority priority) {
return '''
$_lineBreak
${emojis[priority]} *$title - [$number]($url)*
> +$additions -$deletions | files: $changedFiles
${body.replaceAll(_legal, '').trim()}
*Reviewers:*
$_lineBreak''';
}
}

enum ReviewPriority { urgent, high, normal, low }

/// Command to clean all temporary files in the repo.
class ReviewCommand extends AmplifyCommand {
ReviewCommand() {
argParser
..addOption(
'ref',
abbr: 'r',
help:
'The PR reference. Can be a PR number or URL. Defaults to the current branch.',
)
..addOption(
'priority',
abbr: 'p',
help: 'Determines the emoji to use for the PR review.',
allowed: ReviewPriority.values.map((e) => e.name).toList(),
defaultsTo: ReviewPriority.normal.name,
);
}
@override
String get description => 'Creates PR review text for slack';

@override
String get name => 'review';

/// The PR ref to review.
late final ref = argResults?['ref'] as String? ?? '';

/// Determines the emoji to use for the PR review.
late final priority = ReviewPriority.values.firstWhere(
(e) => e.name == argResults!['priority'] as String,
orElse: () => ReviewPriority.normal,
);

/// Verifies that the PR is ready using gh CLI.
Future<void> _verifyPR(String ref) async {
final branchName = await Process.run(
'gh',
['pr', 'ready', ref],
);
if (branchName.exitCode != 0) {
final error = branchName.stderr.toString();
if (error.contains('Could not resolve to a PullRequest')) {
logger.error('A PR is not ready at ref: $ref');
} else {
logger.error('Invalid ref: $error');
}
exit(1);
}
}

/// Fetches PR data from GitHub using gh CLI.
Future<GitHubData> _getGitHubData(String ref) async {
final process = await Process.run(
'gh',
[
'pr',
'view',
ref,
'--json',
'title,body,number,changedFiles,additions,deletions,url',
],
);
if (process.exitCode != 0) {
logger.error('Could not fetch PR data from GitHub');
exit(1);
}
if (process.stdout is! String) {
logger.error('GitHub data is not a string');
exit(1);
}

final prDataJson =
jsonDecode(process.stdout as String) as Map<String, dynamic>;
final prData = GitHubData.fromJson(prDataJson);
return prData;
}

@override
Future<void> run() async {
await super.run();

await _verifyPR(ref);

final ghData = await _getGitHubData(ref);

final slackString = ghData.toMarkdown(priority);

logger
..info(slackString)
..info('After pasting into slack, press CMD+SHIFT+F to format the text.')
..info('Review output was successful!');
}
}
Loading