toFrankie / blog

种一棵树,最好的时间是十年前。其次,是现在。
20 stars 1 forks source link

GitHub GraphQL API 分页查询 #323

Open toFrankie opened 9 months ago

toFrankie commented 9 months ago

配图源自 Freepik

背景

前不久做 Github Blogger 插件开发时,遇到分页查询的场景,是用于查询 Issue 的。

使用 Github REST API 的话,可以很方便地分页查询,比如:

const {Octokit} = require('@octokit/core')

const octokit = new Octokit({
  auth: 'TOKEN',
})

await octokit.request('GET /repos/{owner}/{repo}/issues', {
  owner: 'OWNER',
  repo: 'REPO',
  per_page: 20,
  page: 1,
})

Related Link: List repository issues

由于它并没有提供 in:title 等更加复杂的搜索方式,只能用 GitHub GraphQL API 实现该需求。

简介

我的需求是根据 titlelabel 来筛选 Issues,并支持分页查询。使用到的 API 是下面这个(Queries search):

借助 afterfirstquery 就能实现,比如:

query Issues {
  search(
    type: ISSUE
    query: "user:toFrankie repo:blog state:open label:2023"
    first: 5
    after: null
  ) {
    issueCount
    pageInfo {
      startCursor
      endCursor
      hasNextPage
      hasPreviousPage
    }
    edges {
      node {
        ... on Issue {
          title
        }
      }
    }
  }
}

如果对 Github Graph API 不熟的话,可以借助 Github 提供的 Explorer 进行验证,不懂可看 Using the Explorer

其实从这里就看得出大概了,结果中的 pageInfo 结构如下

也就是说,如果已知 startCursorendCursor 就能代入 Search 的 afterbefore 参数实现翻页的需求。

如果是按顺序从第一页、第二页......进行翻页的话,从本页的 pageInfo 中取出 endCursor 代入下一个查询的 after 中就行。如果我要从第 1 页调到第 5 页呢?

我试图去寻找 Cursor 的规律,从 Introduction to GraphQL 可知,Github 采用 GraphQL 的 cursor-based pagination 方式以实现翻页。

原来 Cursor 是用 Base64 编码后的字符串。

Y3Vyc29yOjU=
cursor:5

解码后可知是 cursor:offset 的形式,所以,实现跳页只要将其编码为 Base64 再传入 after 即可。如果是首页,可以不传入 after 参数或设为 null

示例

其中 TOKENUSERREPOLABEL 以及 query 等按需调整。

const {Octokit} = require('@octokit/core')
const {encode} = require('js-base64')

const octokit = new Octokit({
  auth: 'TOKEN',
})

const page = 2
const perPage = 5
const offset = (page - 1) * perPage
const pageCursor = offset > 0 ? encode(`cursor:${offset}`) : null

await octokit.graphql(`
  {
    search(
      type: ISSUE
      query: "user:USER repo:REPO state:open label:LABEL"
      first: ${perPage}
      after: ${pageCursor ? `"${pageCursor}"` : null}
    ) {
      issueCount
      pageInfo {
        startCursor
        endCursor
        hasNextPage
        hasPreviousPage
      }
      edges {
        node {
          ... on Issue {
            title
          }
        }
      }
    }
  }
`)

RunKit Demo

References