最近又用 Github Actions 玩出了花样。这里记录一下通过 Github Api 和 Github Actions 怎么去做组织内的 commits 统计。
之前一直有一个想法,能不能通过 github 的 api 去将组织内的工作量做成报表以方便查看每个人工作的进度以及各个 issue 的进展。做了简单的调研,发现现有的东西都不太能满足具体的需求:
然后就去评估了下自己做统计的工作量。github 早已经发布了 GraphQL 的 API 并且通过 explorer 去测试和使用其 API 的体验是相当好于是就心动了,决定自己做这个统计。
报表数据主要包括每个人的活跃数据以及每个人所涉及的 issue。
首先,我集成的是 Github 的 GraphQL API 主要用到了以下几个接口:
query Repositories($username: String!, $cursor: String) {
  organization(login: $username) {
    repositories(
      first: 100
      isLocked: false
      orderBy: { field: PUSHED_AT, direction: DESC }
      after: $cursor
    ) {
      pageInfo {
        hasNextPage
        endCursor
      }
      edges {
        node {
          pushedAt
          name
        }
      }
    }
  }
}query Commits($owner: String!, $name: String!, $cursor: String) {
  repository(owner: $owner, name: $name) {
    defaultBranchRef {
      target {
        ... on Commit {
          history(first: 30, after: $cursor) {
            pageInfo {
              hasNextPage
              endCursor
            }
            edges {
              node {
                author {
                  user {
                    name
                    login
                  }
                }
                commitUrl
                abbreviatedOid
                pushedDate
                additions
                deletions
                message
              }
            }
          }
        }
      }
    }
  }
}query Issue($owner: String!, $name: String!, $number: Int!) {
  repository(owner: $owner, name: $name) {
    issue(number: $number) {
      title
      url
    }
  }
}使用的 Client 就是 Apollo GraphQL Client。
原始的 Commit 包含行的增加和删减,但这里有个问题:
这里我处理了前两个,第三个感觉有点复杂,并且我也觉得没必要如此仔细的量化。
node-ignore 去过滤掉那些生成代码node-ignore 是一个很不错的库,实现了 gitignore 的标准,用这个我们就可以将每个 commit 的每个文件扫一遍,过滤掉那些模板的文件后重新统计删减就好了。
这里吐槽 Github GraphQL API 发现其并没有实现具体一个 commit 下文件修改的接口,还需要我去调用 REST 的接口。
这里似乎有点不太严谨,但是似乎问题不大吧...
自从用了 react 似乎很少和各种模板引擎打交道了。上次使用类似的东西还是用 freemarker 做简单的文本内容模板。这次我也做了简单的调研,看了 mustache handlebars 和 ejs 最后还是觉得 ejs 这种可以直接写 js 的比较舒服。尤其是我在尝试 mustache 的时候没找到去迭代 Object 的方法...
写出来就是这个样子,实在是好久没写 nodejs 以及对 ejs 也是现学现用...
## Commit Summary
<% Object.keys(userCommitSummary).forEach(function(username) { -%>
- <%= username %>: <%= userCommitSummary[username].commitCount %> commits +<%= userCommitSummary[username].additions %> -<%= userCommitSummary[username].deletions %>
<% }); -%>
## Issue Summary
<% Object.keys(userIssueSummary).forEach(function(username) { -%>
### <%= username %>
工作涉及 <%= userIssueSummary[username].issueCount %> 个 issue:
<% userIssueSummary[username].issues.forEach(function(issue) { %>
- <%= issue -%>
<% }); %>
<% }); -%>
## Commit Details
<% Object.keys(userRepositoryCommitsDetails).forEach(function(username) { -%>
### <%= username %>
  <% Object.keys(userRepositoryCommitsDetails[username]).forEach(function(repo) { %>
####  <%= repo %>
    <%_ userRepositoryCommitsDetails[username][repo].forEach(function(commit) { -%>
- <%= commit.url %> - <%= commit.message %> <<%= commit.pushedDate %>>
    <%_ }); -%>
  <% }); -%>
<% }); -%>name: statistics of github org
on:
  workflow_dispatch:
    inputs:
      timezone:
        description: "TimeZone default is +8"
        required: true
        default: "Asia/Shanghai"
      date:
        description: "Any date in the week, default is now"
        required: false
  schedule:
    # https://crontab.guru
    - cron: 0 18 * * *
jobs:
  report:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Setup node
        uses: actions/setup-node@v2
        with:
          node-version: "14"
          check-latest: true
      - run: yarn
      - name: generate report
        env:
          GITHUB_TOKEN: ${{ secrets.GH_PAT }}
        run: TZ=${{github.event.inputs.timezone}} DATE=${{github.event.inputs.date}} node main.js
      - name: Upload results
        uses: actions/upload-artifact@v2
        with:
          name: reports
          path: |
            rawdata.json
            report.md
      - name: Prepare Issue Title
        id: issue_title
        run: |
          export TZ=${{github.event.inputs.timezone}}
          export DATE=${{github.event.inputs.date}}
          echo ::set-output name=ISSUE_TITLE::$(node week.js)
      - name: Create Issue From File
        uses: aisensiy/create-issue-from-file@af97df85a971093700b62d1bde8339c4fabb35ff
        with:
          title: Weekly Statistics ${{steps.issue_title.outputs.ISSUE_TITLE}}
          token: ${{ secrets.GH_PAT }}
          update-existing: true
          content-filepath: ./report.md
          labels: |
            report
首先,这里使用了两个 github workflow 的 trigger:
然后,这里在完成统计后(就是 generate report 这个步骤)将所有的数据做成了 artifact 保存了下来,方便后续的数据的追溯。
最后,通过一个 create-issue-from-file 的 github action 将 report 的内容创建成一个 issue 展示出来,展示出来就是上文的截图的效果了。
这里我为了避免每次执行都创建重复性的内容还特意把原来的 action 做了修改,提交了 PR https://github.com/aisensiy/create-issue-from-file 。