Future _sendStatusUpdates()

in app_dart/lib/src/request_handlers/push_gold_status_to_github.dart [55:197]


  Future<void> _sendStatusUpdates(
    DatastoreService datastore,
    RepositorySlug slug,
  ) async {
    final GitHub gitHubClient = await config.createGitHubClient(slug: slug);
    final List<GithubGoldStatusUpdate> statusUpdates = <GithubGoldStatusUpdate>[];
    log.fine('Beginning Gold checks...');
    await for (PullRequest pr in gitHubClient.pullRequests.list(slug)) {
      assert(pr.number != null);
      // Get last known Gold status from datastore.
      final GithubGoldStatusUpdate lastUpdate = await datastore.queryLastGoldUpdate(slug, pr);
      CreateStatus statusRequest;

      log.fine('Last known Gold status for $slug#${pr.number} was with sha: '
          '${lastUpdate.head}, status: ${lastUpdate.status}, description: ${lastUpdate.description}');

      if (lastUpdate.status == GithubGoldStatusUpdate.statusCompleted && lastUpdate.head == pr.head!.sha) {
        log.fine('Completed status already reported for this commit.');
        // We have already seen this commit and it is completed or, this is not
        // a change staged to land on master, which we should ignore.
        continue;
      }

      final String defaultBranch = Config.defaultBranch(slug);
      if (pr.base!.ref != defaultBranch) {
        log.fine('This change is not staged to land on $defaultBranch, skipping.');
        // This is potentially a release branch, or another change not landing
        // on master, we don't need a Gold check.
        continue;
      }

      if (pr.draft!) {
        log.fine('This pull request is a draft.');
        // We don't want to query Gold while a PR is in a draft state, and we
        // don't want to needlessly hold a pending state either.
        // If a PR has been marked `draft` after the fact, and there has not
        // been a new commit, we cannot rescind a previously posted status, so
        // if it is already pending, we should make the contributor aware of
        // that fact.
        if (lastUpdate.status == GithubGoldStatusUpdate.statusRunning &&
            lastUpdate.head == pr.head!.sha &&
            !await _alreadyCommented(gitHubClient, pr, slug, config.flutterGoldDraftChange)) {
          await gitHubClient.issues
              .createComment(slug, pr.number!, config.flutterGoldDraftChange + config.flutterGoldAlertConstant(slug));
        }
        continue;
      }

      log.fine('Querying builds for pull request #${pr.number} with sha: ${lastUpdate.head}...');
      final GraphQLClient gitHubGraphQLClient = await config.createGitHubGraphQLClient();
      final List<String> incompleteChecks = <String>[];
      bool runsGoldenFileTests = false;
      final Map<String, dynamic> data = (await _queryGraphQL(
        gitHubGraphQLClient,
        slug,
        pr.number!,
      ))!;
      final Map<String, dynamic> prData = data['repository']['pullRequest'] as Map<String, dynamic>;
      final Map<String, dynamic> commit = prData['commits']['nodes'].single['commit'] as Map<String, dynamic>;
      List<Map<String, dynamic>>? checkRuns;
      if (commit['checkSuites']['nodes'] != null && (commit['checkSuites']['nodes'] as List<dynamic>).isNotEmpty) {
        checkRuns =
            (commit['checkSuites']['nodes']?.first['checkRuns']['nodes'] as List<dynamic>).cast<Map<String, dynamic>>();
      }
      checkRuns = checkRuns ?? <Map<String, dynamic>>[];
      log.fine('This PR has ${checkRuns.length} checks.');
      for (Map<String, dynamic> checkRun in checkRuns) {
        log.fine('Check run: $checkRun');
        final String name = checkRun['name'].toLowerCase() as String;
        if (name.contains('framework') || name.contains('web engine')) {
          runsGoldenFileTests = true;
        }
        if (checkRun['conclusion'] == null || checkRun['conclusion'].toUpperCase() != 'SUCCESS') {
          incompleteChecks.add(name);
        }
      }

      if (runsGoldenFileTests) {
        log.fine('This PR executes golden file tests.');
        // Check when this PR was last updated. Gold does not keep results after
        // >20 days. If a PR has gone stale, we should draw attention to it to be
        // updated or closed.
        final DateTime updatedAt = pr.updatedAt!.toUtc();
        final DateTime twentyDaysAgo = DateTime.now().toUtc().subtract(const Duration(days: 20));
        if (updatedAt.isBefore(twentyDaysAgo)) {
          log.fine('Stale PR, no gold status to report.');
          if (!await _alreadyCommented(gitHubClient, pr, slug, config.flutterGoldStalePR)) {
            log.fine('Notifying for stale PR.');
            await gitHubClient.issues
                .createComment(slug, pr.number!, config.flutterGoldStalePR + config.flutterGoldAlertConstant(slug));
          }
          continue;
        }

        if (incompleteChecks.isNotEmpty) {
          // If checks on an open PR are running or failing, the gold status
          // should just be pending. Any draft PRs are skipped
          // until marked ready for review.
          log.fine('Waiting for checks to be completed.');
          statusRequest =
              _createStatus(GithubGoldStatusUpdate.statusRunning, config.flutterGoldPending, slug, pr.number!);
        } else {
          // We do not want to query Gold on a draft PR.
          assert(!pr.draft!);
          // Get Gold status.
          final String goldStatus = await _getGoldStatus(slug, pr);
          statusRequest = _createStatus(
              goldStatus,
              goldStatus == GithubGoldStatusUpdate.statusRunning
                  ? config.flutterGoldChanges
                  : config.flutterGoldSuccess,
              slug,
              pr.number!);
          log.fine('New status for potential update: ${statusRequest.state}, ${statusRequest.description}');
          if (goldStatus == GithubGoldStatusUpdate.statusRunning &&
              !await _alreadyCommented(gitHubClient, pr, slug, config.flutterGoldCommentID(pr))) {
            log.fine('Notifying for triage.');
            await _commentAndApplyGoldLabels(gitHubClient, pr, slug);
          }
        }

        // Push updates if there is a status change (detected by unique description)
        // or this is a new commit.
        if (lastUpdate.description != statusRequest.description || lastUpdate.head != pr.head!.sha) {
          try {
            log.fine('Pushing status to GitHub: ${statusRequest.state}, ${statusRequest.description}');
            await gitHubClient.repositories.createStatus(slug, pr.head!.sha!, statusRequest);
            lastUpdate.status = statusRequest.state!;
            lastUpdate.head = pr.head!.sha;
            lastUpdate.updates = (lastUpdate.updates ?? 0) + 1;
            lastUpdate.description = statusRequest.description!;
            statusUpdates.add(lastUpdate);
          } catch (error) {
            log.severe('Failed to post status update to ${slug.fullName}#${pr.number}: $error');
          }
        }
      } else {
        log.fine('This PR does not execute golden file tests.');
      }
    }
    await datastore.insert(statusUpdates);
    log.fine('Committed all updates for $slug');
  }