プルリクエストが承認+テストをパスしたら通知するmacOSアプリを作った

プルリクエストがマージ可能になったらいち早く知りたいので、承認+テストをパスしたら通知を送るmacOSのアプリを作った。

github.com

approve不要なPRについてはテストが完了した時点で通知がくる(はず)。 テストがこけても通知が来る。rejectされても通知が来る。要はステータスが確定したら通知が来る。

Notificationsをみてもいいんだけど、approveやテスト以外の通知もくるので専用に作ってみた。 Notifications全般の通知を受け取りたいならGitifyを使った方がいいと思う。

通知はこんな感じ。

メニューバーのアイコンをクリックするとステータスが確定したPRの一覧が出る。

あとステータスが確定したPRがないときは、メニューバーのアイコンが黒に変わる。

設定画面で検索条件を変えられるので、特定のorgだけにするとか、一部orgを除外するとかできる。

実装

以下のようなGraphQLを実行して、PR一覧とテストのステータスをまとめて取得している。

https://github.com/winebarrel/Succ/blob/main/Succ/Github/SearchPullRequests.graphql

query SearchPullRequests($query: String!) {
  search(type: ISSUE, last: 100, query: $query) {
    nodes {
      ... on PullRequest {
        repository {
          name
          owner {
            login
          }
        }
        title
        url
        reviewDecision
        commits(last: 1) {
          nodes {
            commit {
              url
              statusCheckRollup {
                state
              }
            }
          }
        }
      }
    }
  }
}

取得したPRのステータスを見て通知するかどうかを判断。

https://github.com/winebarrel/Succ/blob/main/Succ/PullRequest.swift

    private func updateNodes(_ value: GraphQLResult<Github.SearchPullRequestsQuery.Data>) {
        var fetchedNodes: Nodes = []

        value.data?.search.nodes?.forEach { body in
            if let pull = body?.asPullRequest {
                let reviewDecision = pull.reviewDecision

                if reviewDecision != nil && reviewDecision != .approved && reviewDecision != .changesRequested {
                    return
                }

                guard let commit = pull.commits.nodes?.first??.commit else {
                    return
                }

                guard let state = commit.statusCheckRollup?.state else {
                    return
                }

                if state != .success && state != .failure && state != .error {
                    return
                }

                let node = Node(
                    owner: pull.repository.owner.login,
                    repo: pull.repository.name,
                    title: pull.title,
                    url: pull.url,
                    reviewDecision: reviewDecision?.rawValue ?? "",
                    state: state.rawValue,
                    commitUrl: commit.url,
                    success: (reviewDecision == nil || reviewDecision == .approved) && state == .success
                )

                fetchedNodes.append(node)
            }
        }