ドキュメント読めば一通りわかるが一応やったこととか残しておく
今回はGitLabのMergeRequestの情報をAPIで取得する
一定期間内で、特定のリポジトリのMergeRequestの内容で、MergeRequestにひもづくコミットのshaと日時も欲しかったのでREST APIではなくGraphQLを使うことにした
GraphQLのExplorer
https://{GitLabのホスト}/-/graphql-explorer
にアクセスすることでGraphQLのExplorerにアクセスできる
Explorerだと型の情報などサジェストしてくれるので、クエリ内容を考えるときはExplorerで試行錯誤しながら考えていくとサクサク進められる
否定の表現
MergeRequestに対して、特定のauthorUsername
以外を取得したいといったケースがでてきたので調べてみた
GraphQL API style guide | GitLab
表現方法は上記ドキュメントに載っているとおりmergeRequests(not: {authUsername: "hoge"})
という書き方で表現する
が、フィールドによってそもそもnot
に対応していない場合があるので確認する必要がある
GraphQLのExplorerでMergeRequests(state: merged, not{})
と書き、not
にカーソルを置くとサジェストが出てくるProject.mergeRequests(not: MergeRequestsResolverNegatedParams)
のでクリックして詳しく見てみる
こんな感じだった
labels,milestoneTitleしか指定できなさそう…
ということで断念した(取得後どこかでフィルタリングするという方針にした)
CLIから実行する
Get started with GitLab GraphQL API | GitLab
必要なheaderやTokenを把握する
GITLAB_TOKENは管理画面からパーソナルアクセストークンを取得して設定する
試しに適当なクエリをなげてみる
$ curl -v -XPOST "https://gitlab.example.com/api/graphql" --header "Authorization: Bearer $GITLAB_TOKEN" --header "Content-Type: application/json" --data-binary "@./sample.gql.json"
- sample.gql.json
{ "query": "query {currentUser {name}}", "variables": {} }
--data-binaryには先頭に@
を付けてファイルを指定することもできる
- 結果
{ "data": { "currentUser": { "name": "me" } } }
クエリを投げられることがわかった
あとはクエリの中身を色々書いていくだけ
Variablesの渡し方
JSONで渡すパラメータを次のようにする
{ "query": ".....", "variables": null }
参考: GraphQL Filters Not Working in cURL POST Data - How to Use GitLab - GitLab Forum
話している内容は自分が求めていたものと違うが実際にクエリの投げ方などが書いてあったので参考になった
スクリプトを書く
ここまでの話を踏まえてスクリプトを書く
zxで書いてみる
- request.mjs
GraphQLのクエリを別で記述しファイル内容を読み出して他のパラメータと一緒にJSON文字列として出力する(エスケープとか気にしなくてよいので)
また、100件以上必要になる場合があるので1度目のリクエストでendCursor
を取得し次のページがある場合はhasNextPage
をみて再帰で次のcursor
を指定してリクエストを送るようにした
レスポンスのJSONはこの時点では変更を加えず、特定のディレクトリ以下にそのまま保存する、フィルタリングなり整形処理なりは別工程で行う
const args = process.argv.slice(3); // 0 hoge/fuga const fullPath = args[0]; const repo = fullPath.replace("/", "-"); if (!process.env.GITLAB_TOKEN) { throw "required environment GITLAB_TOKEN."; } const request = async (fullPath, cursor) => { const gqlQuery = fs.readFileSync('graphql/merge_requests.graphql', 'utf-8').toString(); const body = { query: gqlQuery, variables: { fullPath: fullPath, cursor: cursor } }; const res = await fetch("https://gitlab.example.com/api/graphql", { method: "POST", headers: { "Authorization": `Bearer ${process.env.GITLAB_TOKEN}`, "Content-Type": "application/json" }, body: JSON.stringify(body) }).then(res => res.json()); const hasNext = res.data.project.mergeRequests.pageInfo.hasNextPage; const endCursor = res.data.project.mergeRequests.pageInfo.endCursor; $`echo ${JSON.stringify(res)} > api/${repo}-${endCursor}.json`; await sleep(1000); if (hasNext) { await request(fullPath, endCursor) } } await request(fullPath, null);
※(実際のホスト名は違うホスト名)
GraphQLのクエリ
- graphql/merge_requests.graphql
createdAfter
の指定が固定値にしてしまっていたりするがほかは外から渡せるようにした
$cursor
がnull許容なのは初回リクエスト時のため(初回リクエスト時は指定しない)
query ($fullPath: ID! $cursor:String) { project(fullPath: $fullPath) { mergeRequests(state: merged, createdAfter: "2021-01-01T00:00:00", after: $cursor) { pageInfo { endCursor hasNextPage } edges { cursor node { iid state createdAt mergedAt title webUrl diffStatsSummary { additions deletions fileCount } assignees { edges { node { name username } } } author { name username } commitCount commitsWithoutMergeCommits(last: 1) { nodes { sha author { id name username } authoredDate } } } } } } }
commitsWithoutMergeCommits(last: 1)
でひもづく初回のコミットを取得するようにしている
いくつかのケースで確認したがこれで問題なさそう(もしかしたら後日修正しているかも)
- 実行
zx request.mjs hoge/fuga
api/
以下にAPIのレスポンスがファイル出力された
実際の中身はこんな感じ
{ "data": { "project": { "mergeRequests": { "pageInfo": { "endCursor": "hogehoge", "hasNextPage": false }, "edges": [ { "cursor": "hogefuga1", "node": { "iid": "2323", "state": "merged", "createdAt": "2022-06-15T09:26:48Z", "mergedAt": "2022-06-15T09:28:56Z", "title": "title1", "webUrl": "https://gitlab.example.com/hoge/fuga/-/merge_requests/2323", "diffStatsSummary": { "additions": 86, "deletions": 0, "fileCount": 14 }, "assignees": { "edges": [] }, "author": { "name": "ユーザー1", "username": "user1" }, "commitCount": 18, "commitsWithoutMergeCommits": { "nodes": [ { "sha": "sha", "author": { "id": "gid://gitlab/User/111", "name": "ユーザー1", "username": "user1" }, "authoredDate": "2022-06-02T17:50:06+09:00" } ] } } }, { "cursor": "hogefuga2", "node": { "iid": "2322", "state": "merged", "createdAt": "2022-06-14T11:47:44Z", "mergedAt": "2022-06-22T05:32:28Z", "title": "title2", "webUrl": "https://gitlab.example.com/hoge/fuga/-/merge_requests/2322", "diffStatsSummary": { "additions": 47, "deletions": 30, "fileCount": 16 },
ばっちり
まとめ
- GitLabのAPIにGraphQLがあったので試した
- Explorerで型情報を把握できるのでクエリを考えるときはExplorerが便利
- zxを使ってちょっとしたスクリプトを書いた
- 結構便利、READMEさらっと読んだが追加でライブラリを入れずとも、YAMLが読めたりSleepがあったりするのでzxだけでこと足りるケースがそれなりにありそう、しばらく色々な用途で使ってみようと思っている