|
4 | 4 | "fmt" |
5 | 5 | "io" |
6 | 6 | "regexp" |
| 7 | + "sort" |
7 | 8 | "strconv" |
8 | 9 | "strings" |
9 | 10 |
|
@@ -429,10 +430,13 @@ func printPrPreview(out io.Writer, pr *api.PullRequest) error { |
429 | 430 | pr.BaseRefName, |
430 | 431 | pr.HeadRefName, |
431 | 432 | ))) |
| 433 | + fmt.Fprintln(out) |
432 | 434 |
|
433 | 435 | // Metadata |
434 | | - // TODO: Reviewers |
435 | | - fmt.Fprintln(out) |
| 436 | + if reviewers := prReviewerList(*pr); reviewers != "" { |
| 437 | + fmt.Fprint(out, utils.Bold("Reviewers: ")) |
| 438 | + fmt.Fprintln(out, reviewers) |
| 439 | + } |
436 | 440 | if assignees := prAssigneeList(*pr); assignees != "" { |
437 | 441 | fmt.Fprint(out, utils.Bold("Assignees: ")) |
438 | 442 | fmt.Fprintln(out, assignees) |
@@ -466,6 +470,116 @@ func printPrPreview(out io.Writer, pr *api.PullRequest) error { |
466 | 470 | return nil |
467 | 471 | } |
468 | 472 |
|
| 473 | +// Ref. https://developer.github.com/v4/enum/pullrequestreviewstate/ |
| 474 | +const ( |
| 475 | + requestedReviewState = "REQUESTED" // This is our own state for review request |
| 476 | + approvedReviewState = "APPROVED" |
| 477 | + changesRequestedReviewState = "CHANGES_REQUESTED" |
| 478 | + commentedReviewState = "COMMENTED" |
| 479 | +) |
| 480 | + |
| 481 | +type reviewerState struct { |
| 482 | + Name string |
| 483 | + State string |
| 484 | +} |
| 485 | + |
| 486 | +// colorFuncForReviewerState returns a color function for a reviewer state |
| 487 | +func colorFuncForReviewerState(state string) func(string) string { |
| 488 | + switch state { |
| 489 | + case requestedReviewState: |
| 490 | + return utils.Yellow |
| 491 | + case approvedReviewState: |
| 492 | + return utils.Green |
| 493 | + case changesRequestedReviewState: |
| 494 | + return utils.Red |
| 495 | + case commentedReviewState: |
| 496 | + return func(str string) string { return str } // Do nothing |
| 497 | + default: |
| 498 | + return nil |
| 499 | + } |
| 500 | +} |
| 501 | + |
| 502 | +// formattedReviewerState formats a reviewerState with state color |
| 503 | +func formattedReviewerState(reviewer *reviewerState) string { |
| 504 | + stateColorFunc := colorFuncForReviewerState(reviewer.State) |
| 505 | + return fmt.Sprintf("%s (%s)", reviewer.Name, stateColorFunc(strings.ReplaceAll(strings.Title(strings.ToLower(reviewer.State)), "_", " "))) |
| 506 | +} |
| 507 | + |
| 508 | +// prReviewerList generates a reviewer list with their last state |
| 509 | +func prReviewerList(pr api.PullRequest) string { |
| 510 | + reviewerStates := parseReviewers(pr) |
| 511 | + reviewers := make([]string, 0, len(reviewerStates)) |
| 512 | + |
| 513 | + sortReviewerStates(reviewerStates) |
| 514 | + |
| 515 | + for _, reviewer := range reviewerStates { |
| 516 | + reviewers = append(reviewers, formattedReviewerState(reviewer)) |
| 517 | + } |
| 518 | + |
| 519 | + reviewerList := strings.Join(reviewers, ", ") |
| 520 | + |
| 521 | + return reviewerList |
| 522 | +} |
| 523 | + |
| 524 | +// Ref. https://developer.github.com/v4/union/requestedreviewer/ |
| 525 | +const teamTypeName = "Team" |
| 526 | + |
| 527 | +const ghostName = "ghost" |
| 528 | + |
| 529 | +// parseReviewers parses given Reviews and ReviewRequests |
| 530 | +func parseReviewers(pr api.PullRequest) []*reviewerState { |
| 531 | + reviewerStates := make(map[string]*reviewerState) |
| 532 | + |
| 533 | + for _, review := range pr.Reviews.Nodes { |
| 534 | + if review.Author.Login != pr.Author.Login { |
| 535 | + name := review.Author.Login |
| 536 | + if name == "" { |
| 537 | + name = ghostName |
| 538 | + } |
| 539 | + reviewerStates[name] = &reviewerState{ |
| 540 | + Name: name, |
| 541 | + State: review.State, |
| 542 | + } |
| 543 | + } |
| 544 | + } |
| 545 | + |
| 546 | + // Overwrite reviewer's state if a review request for the same reviewer exists. |
| 547 | + for _, reviewRequest := range pr.ReviewRequests.Nodes { |
| 548 | + name := reviewRequest.RequestedReviewer.Login |
| 549 | + if reviewRequest.RequestedReviewer.TypeName == teamTypeName { |
| 550 | + name = reviewRequest.RequestedReviewer.Name |
| 551 | + } |
| 552 | + reviewerStates[name] = &reviewerState{ |
| 553 | + Name: name, |
| 554 | + State: requestedReviewState, |
| 555 | + } |
| 556 | + } |
| 557 | + |
| 558 | + // Convert map to slice for ease of sort |
| 559 | + result := make([]*reviewerState, 0, len(reviewerStates)) |
| 560 | + for _, reviewer := range reviewerStates { |
| 561 | + result = append(result, reviewer) |
| 562 | + } |
| 563 | + |
| 564 | + return result |
| 565 | +} |
| 566 | + |
| 567 | +// sortReviewerStates puts completed reviews before review requests and sorts names alphabetically |
| 568 | +func sortReviewerStates(reviewerStates []*reviewerState) { |
| 569 | + sort.Slice(reviewerStates, func(i, j int) bool { |
| 570 | + if reviewerStates[i].State == requestedReviewState && |
| 571 | + reviewerStates[j].State != requestedReviewState { |
| 572 | + return false |
| 573 | + } |
| 574 | + if reviewerStates[j].State == requestedReviewState && |
| 575 | + reviewerStates[i].State != requestedReviewState { |
| 576 | + return true |
| 577 | + } |
| 578 | + |
| 579 | + return reviewerStates[i].Name < reviewerStates[j].Name |
| 580 | + }) |
| 581 | +} |
| 582 | + |
469 | 583 | func prAssigneeList(pr api.PullRequest) string { |
470 | 584 | if len(pr.Assignees.Nodes) == 0 { |
471 | 585 | return "" |
|
0 commit comments