private async assessNeedsReview()

in tools/@aws-cdk/prlint/lint.ts [58:174]


  private async assessNeedsReview(): Promise<LinterActions> {
    const pr = await this.pr();

    const reviewsData = await this.client.paginate(this.client.pulls.listReviews, this.prParams);
    console.log(JSON.stringify(reviewsData.map(r => ({
      user: r.user?.login,
      state: r.state,
      author_association: r.author_association,
      submitted_at: r.submitted_at,
    })), undefined, 2));

    // NOTE: MEMBER = a member of the organization that owns the repository
    // COLLABORATOR = has been invited to collaborate on the repository
    const maintainerRequestedChanges = reviewsData.some(
      review => review.author_association === 'MEMBER'
        && review.user?.login !== 'aws-cdk-automation'
        && review.state === 'CHANGES_REQUESTED',
    );
    const maintainerApproved = reviewsData.some(
      review => review.author_association === 'MEMBER'
        && review.state === 'APPROVED',
    );

    // NOTE: community reviewers may approve, comment, or request changes; however, it
    // is possible for the same member to perform any combination of those actions on
    // a single PR. We solve this by:
    //   1. Filtering reviews to those by trusted community members
    //   2. Filtering out reviews that only leave comments (without approving or requesting changes).
    //      This allows a reviewer to participate in a conversation about their review without
    //      effectively dismissing their review. While GitHub does not allow community reviewers
    //      to dismiss their reviews (which requires privileges on the repo), they can leave a
    //      new review with the opposite approve/request state to update their review.
    //   3. Mapping reviewers to only their newest review
    //   4. Checking if any reviewers' most recent review is an approval
    //      -> If so, the PR is considered community approved; the approval can always
    //         be dismissed by a maintainer to respect another reviewer's requested changes.
    //   5. Checking if any reviewers' most recent review requested changes
    //      -> If so, the PR is considered to still need changes to meet community review.
    const trustedCommunityMembers = await this.getTrustedCommunityMembers();

    const reviewsByTrustedCommunityMembers = reviewsData
      .filter(review => trustedCommunityMembers.includes(review.user?.login ?? ''))
      .filter(review => review.state !== 'PENDING' && review.state !== 'COMMENTED')
      .reduce((grouping, review) => {
        // submitted_at is not present for PENDING comments but is present for other states.
        // Because of that, it is optional on the type but sure to be present here. Likewise,
        // review.user is sure to be defined because we're operating on reviews by trusted
        // community members
        let newest = grouping[review.user!.login] ?? review;
        if (review.submitted_at! > newest.submitted_at!) {
          newest = review;
        }

        return {
          ...grouping,
          [review.user!.login]: newest,
        };
      }, {} as Record<string, typeof reviewsData[0]>);
    console.log('raw data: ', JSON.stringify(reviewsByTrustedCommunityMembers));
    const communityApproved = Object.values(reviewsByTrustedCommunityMembers).some(({state}) => state === 'APPROVED');
    const communityRequestedChanges = !communityApproved && Object.values(reviewsByTrustedCommunityMembers).some(({state}) => state === 'CHANGES_REQUESTED')

    const prLinterFailed = reviewsData.find((review) => review.user?.login === 'aws-cdk-automation' && review.state !== 'DISMISSED') as Review;
    const userRequestsExemption = pr.labels.some(label => (label.name === Exemption.REQUEST_EXEMPTION || label.name === Exemption.REQUEST_CLARIFICATION));
    console.log('evaluation: ', JSON.stringify({
      draft: pr.draft,
      mergeable_state: pr.mergeable_state,
      prLinterFailed,
      maintainerRequestedChanges,
      maintainerApproved,
      communityRequestedChanges,
      communityApproved,
      userRequestsExemption,
    }, undefined, 2));

    const fixesP1 = pr.labels.some(label => label.name === 'p1');
    let readyForReview = true;
    if (
      // we don't need to review drafts
      pr.draft
        // or PRs with conflicts
        || pr.mergeable_state === 'dirty'
        // or PRs that already have changes requested by a maintainer
        || maintainerRequestedChanges
        // or the PR linter failed and the user didn't request an exemption
        || (prLinterFailed && !userRequestsExemption)
        // or a maintainer has already approved the PR
        || maintainerApproved
        // or a trusted community member has requested changes on a p2 PR
        || (!fixesP1 && communityRequestedChanges)
    ) {
      readyForReview = false;
    }

    // needs-maintainer-review means one of the following
    // 1) fixes a p1 bug
    // 2) is already community approved
    // 3) is authored by a core team member
    if (readyForReview && (fixesP1 || communityApproved || pr.labels.some(label => label.name === 'contribution/core'))) {
      return {
        addLabels: ['pr/needs-maintainer-review'],
        removeLabels: ['pr/needs-community-review'],
      };
    } else if (readyForReview && !fixesP1) {
      return {
        addLabels: ['pr/needs-community-review'],
        removeLabels: ['pr/needs-maintainer-review'],
      };
    } else {
      return {
        removeLabels: [
          'pr/needs-community-review',
          'pr/needs-maintainer-review',
        ],
      };
    }
  }