LoginSignup
7
1

More than 1 year has passed since last update.

Ruboty に GitHub の issue/pull request を検索する command を追加する

Last updated at Posted at 2022-10-21

はじめに

Qiita 社では Slack bot に Ruboty を :qiitan: Qiitan として利用しています。

:qiitan: Qiitan (Ruboty) に GitHub の issue/pull request を検索させるというニーズが出てきたため、 "ruboty-qiita-github" に search issues command をさっと追加しました

今後 :qiitan: Qiitan (Ruboty) に新しい command を追加する時が来たら参考にしてください :information_desk_person:

GitHub の issue/pull request を検索する command を追加する

GitHub API を確認する

まず、 GitHub の issue/pull request を検索する Interface を確認します

GitHub REST API については、以下 GitHub Docs から探しましょう

今回必要な API は Search issues and pull requests です
必要な parameters や query parameters すべて言及することは難しいため 以下 docs を参照してください
Search issues and pull requests | Search - GitHub Docs

また、 query parameters q については、以下 docs を参照することをおすすめです :thumbsup:
Filtering and searching issues and pull requests - GitHub Docs

GitHub REST API に記載されている API は GitHub CLI で簡単に確認できるため確認してみましょう

今回は、 usrt:mziyut (@mziyut がもつ repository) の is:open (issue/pull request が Open されている) の is:public (public な repository) の issue/pull request を 1 件取得するという条件で issue/pull request を検索しました

% gh api -H "Accept: application/vnd.github+json" '/search/issues?q=user:mziyut+is:public+is:open&per_page=1'

実行結果は以下のようになります

Click me
{
  "total_count": 31,
  "incomplete_results": false,
  "items": [
    {
      "url": "https://api.github.com/repos/mziyut/honkit-plugin-prism/issues/7",
      "repository_url": "https://api.github.com/repos/mziyut/honkit-plugin-prism",
      "labels_url": "https://api.github.com/repos/mziyut/honkit-plugin-prism/issues/7/labels{/name}",
      "comments_url": "https://api.github.com/repos/mziyut/honkit-plugin-prism/issues/7/comments",
      "events_url": "https://api.github.com/repos/mziyut/honkit-plugin-prism/issues/7/events",
      "html_url": "https://github.com/mziyut/honkit-plugin-prism/pull/7",
      "id": 1410719907,
      "node_id": "PR_kwDOIJ_6M85A4onc",
      "number": 7,
      "title": "Bump @types/node from 18.8.3 to 18.11.0",
      "user": {
        "login": "dependabot[bot]",
        "id": 49699333,
        "node_id": "MDM6Qm90NDk2OTkzMzM=",
        "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4",
        "gravatar_id": "",
        "url": "https://api.github.com/users/dependabot%5Bbot%5D",
        "html_url": "https://github.com/apps/dependabot",
        "followers_url": "https://api.github.com/users/dependabot%5Bbot%5D/followers",
        "following_url": "https://api.github.com/users/dependabot%5Bbot%5D/following{/other_user}",
        "gists_url": "https://api.github.com/users/dependabot%5Bbot%5D/gists{/gist_id}",
        "starred_url": "https://api.github.com/users/dependabot%5Bbot%5D/starred{/owner}{/repo}",
        "subscriptions_url": "https://api.github.com/users/dependabot%5Bbot%5D/subscriptions",
        "organizations_url": "https://api.github.com/users/dependabot%5Bbot%5D/orgs",
        "repos_url": "https://api.github.com/users/dependabot%5Bbot%5D/repos",
        "events_url": "https://api.github.com/users/dependabot%5Bbot%5D/events{/privacy}",
        "received_events_url": "https://api.github.com/users/dependabot%5Bbot%5D/received_events",
        "type": "Bot",
        "site_admin": false
      },
      "labels": [
        {
          "id": 4678247549,
          "node_id": "LA_kwDOIJ_6M88AAAABFthkfQ",
          "url": "https://api.github.com/repos/mziyut/honkit-plugin-prism/labels/dependencies",
          "name": "dependencies",
          "color": "0366d6",
          "default": false,
          "description": "Pull requests that update a dependency file"
        },
        {
          "id": 4678247557,
          "node_id": "LA_kwDOIJ_6M88AAAABFthkhQ",
          "url": "https://api.github.com/repos/mziyut/honkit-plugin-prism/labels/javascript",
          "name": "javascript",
          "color": "168700",
          "default": false,
          "description": "Pull requests that update Javascript code"
        }
      ],
      "state": "open",
      "locked": false,
      "assignee": null,
      "assignees": [],
      "milestone": null,
      "comments": 0,
      "created_at": "2022-10-17T01:17:11Z",
      "updated_at": "2022-10-17T01:17:13Z",
      "closed_at": null,
      "author_association": "NONE",
      "active_lock_reason": null,
      "draft": false,
      "pull_request": {
        "url": "https://api.github.com/repos/mziyut/honkit-plugin-prism/pulls/7",
        "html_url": "https://github.com/mziyut/honkit-plugin-prism/pull/7",
        "diff_url": "https://github.com/mziyut/honkit-plugin-prism/pull/7.diff",
        "patch_url": "https://github.com/mziyut/honkit-plugin-prism/pull/7.patch",
        "merged_at": null
      },
      "body": "Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 18.8.3 to 18.11.0.\n<details>\n<summary>Commits</summary>\n<ul>\n<li>See full diff in <a href=\"https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node\">compare view</a></li>\n</ul>\n</details>\n<br />\n\n\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@types/node&package-manager=npm_and_yarn&previous-version=18.8.3&new-version=18.11.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)\n\nDependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.\n\n[//]: # (dependabot-automerge-start)\n[//]: # (dependabot-automerge-end)\n\n---\n\n<details>\n<summary>Dependabot commands and options</summary>\n<br />\n\nYou can trigger Dependabot actions by commenting on this PR:\n- `@dependabot rebase` will rebase this PR\n- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it\n- `@dependabot merge` will merge this PR after your CI passes on it\n- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it\n- `@dependabot cancel merge` will cancel a previously requested merge and block automerging\n- `@dependabot reopen` will reopen this PR if it is closed\n- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\n- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\n- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\n\n\n</details>",
      "reactions": {
        "url": "https://api.github.com/repos/mziyut/honkit-plugin-prism/issues/7/reactions",
        "total_count": 0,
        "+1": 0,
        "-1": 0,
        "laugh": 0,
        "hooray": 0,
        "confused": 0,
        "heart": 0,
        "rocket": 0,
        "eyes": 0
      },
      "timeline_url": "https://api.github.com/repos/mziyut/honkit-plugin-prism/issues/7/timeline",
      "performed_via_github_app": null,
      "state_reason": null,
      "score": 1.0
    }
  ]
}

今回、 Issue/Pull request の検索に必要な API が定義され、返却される項目が理解できたら次のステップに進みましょう

Gem octkit を確認する

ruboty-qiita-github では octokit/octokit.rb を利用し GitHub API 経由でデータを取得・操作しています

lib/octokit/client/search.rb#L37-L49
      # Search issues
      #
      # @param query [String] Search term and qualifiers
      # @param options [Hash] Sort and pagination options
      # @option options [String] :sort Sort field
      # @option options [String] :order Sort order (asc or desc)
      # @option options [Integer] :page Page of paginated results
      # @option options [Integer] :per_page Number of items per page
      # @return [Sawyer::Resource] Search results object
      # @see https://developer.github.com/v3/search/#search-issues-and-pull-requests
      def search_issues(query, options = {})
        search 'search/issues', query, options
      end

先ほど GitHub REST API で確認した endpoint を叩けることが確認できました。

ruboty-qiita-github に command を追加する

必要な GitHub API が確認できたら ruboty-qiita-github に command を定義します
command を定義するために以下ファイルを変更、作成しましょう

  • 新規作成
    • lib/ruboty/github/actions/search_issues.rb
  • 変更
    • lib/ruboty/handlers/github.rb
    • lib/ruboty/github.rb

実際のコードを例に役割など説明していきます

lib/ruboty/github/actions/search_issues.rb

まず先に、 search issues の処理を定義しましょう
lib/ruboty/github/actions/search_issues.rb を新規作成します

処理の流れとしては...

  • GitHub Token が登録されているか確認
    • 登録されていない場合、エラーを返却
  • octkit (code 上は client) 経由で search_issues API からデータを取得する
  • 結果を整形して、返却する
lib/ruboty/github/actions/search_issues.rb
# frozen_string_literal: true

module Ruboty
  module Github
    module Actions
      class SearchIssues < Base
        def call
          if has_access_token?
            search
          else
            require_access_token
          end
        end

        private

        def search
          results = search_issues.items.each_with_object(["Searched: '#{query}'"]) do |item, object|
            repository_url = item[:repository_url].delete_prefix('https://api.github.com/repos/')

            object << "[#{repository_url}##{item[:number]}] #{item[:title]} (#{item[:user][:login]})\n#{item[:html_url]}"
          end

          message.reply(results.join("\n"))
        rescue Octokit::Unauthorized
          message.reply('Failed in authentication (401)')
        rescue Octokit::NotFound
          message.reply('Could not find that repository')
        rescue StandardError => e
          message.reply("Failed by #{e.class}")
        end

        def query
          message[:query]
        end

        def search_issues
          client.search_issues(query)
        end
      end
    end
  end
end

処理が定義できたら次のステップに進みましょう

lib/ruboty/github.rb

先ほど作成した lib/ruboty/github/actions/search_issues.rb を require します

lib/ruboty/github.rb
require 'ruboty/github/actions/search_issues'

lib/ruboty/handlers/github.rb

lib/ruboty/handlers/github.rb は Ruboty が受け取ったコマンドを定義された process に dispatch する役割を担っています
今回追加した処理は以下 2 つです

lib/ruboty/handlers/github.rb#L14-L18
      on(
        /search issues "(?<query>.+)"/,  # 追加する command の条件を記載
        name: "search_issues",           # dispatch する method 名を記載
        description: "Search an issue",  # command の説明を記載
      )
lib/ruboty/handlers/github.rb#L54-L56
      def search_issues(message)
        Ruboty::Github::Actions::SearchIssues.new(message).call
      end

ここまでで、 Ruboty に対して search issues "user:foo" 等と送信すると指定した処理が実行されるようになります

その他

必要に応じて今回追加した command に対するテストも書きましょう :muscle:

spec/ruboty/handlers/github_spec.rb

最後に

今回は Ruboty の plugin である ruboty-qiita-github に簡単な command を追加してみました
コマンドを追加するだけであれば簡単なので Ruboty の plugin の開発に try してみてはいかがでしょうか

Reference

7
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
1