Skip to main content

使用 Actions 自动化 Projects

可以使用 GitHub Actions 自动化你的项目。

GitHub Actions 工作流程

本节说明如何使用 GraphQL API 和 GitHub Actions 向组织项目添加拉取请求。 在示例工作流程中,当拉取请求标记为“准备审核”时,项目中会添加一项“状态”字段设置为“待办”的新任务,并且当前日期添加到自定义的“发布日期”字段中。

您可以复制以下工作流程之一,并按照下表中的说明对其进行修改,以满足您的需求。

项目可以跨越多个仓库,但工作流是特定于仓库的。 将工作流添加到希望项目跟踪的每个存储库。有关创建工作流文件的详细信息,请参阅“GitHub Actions 快速入门”。

本文假设您基本了解 GitHub Actions。 有关 GitHub Actions 的详细信息,请参阅“GitHub Actions”。

有关可以通过 API 对项目进行的其他更改的详细信息,请参阅“使用 API 管理项目”。

你可能还希望使用 actions/add-to-project 工作流,该工作流由 GitHub 维护,并将当前问题或拉取请求添加到指定的项目。 有关详细信息,请参阅 actions/add-to-project 存储库和自述文件。

注意:GITHUB_TOKEN 的范围限定为存储库级别,并且无法访问 projects。 若要访问 projects,可以创建 GitHub App(建议用于组织项目)或个人访问令牌(建议用于用户项目)。 下面显示了这两种方法的工作流程示例。

使用 GitHub App 进行身份验证的示例工作流程

  1. 创建 GitHub App 或选择组织拥有的现有 GitHub App。 有关详细信息,请参阅“创建 GitHub App”。

  2. 授予 GitHub App 对组织项目的读取和写入权限。 有关详细信息,请参阅“编辑 GitHub App 的权限”。

    注意:你可以控制应用对组织项目和存储库项目的权限。 您必须授予读取和写入组织项目的权限;读取和写入存储库项目的权限是不够的。

  3. 在组织中安装 GitHub App。 为项目需要访问的所有存储库安装它。 有关详细信息,请参阅“安装GitHub Apps”。

  4. 将 GitHub App 的 ID 作为机密存储在存储库或组织中。 在以下工作流中,将 APP_ID 替换为机密的名称。 您可以在应用的设置页面上或通过应用 API 找到应用 ID。 有关详细信息,请参阅“应用”。

  5. 为应用生成私钥。 将生成的文件的内容作为机密存储在存储库或组织中。 (存储文件的全部内容,包括 -----BEGIN RSA PRIVATE KEY----------END RSA PRIVATE KEY-----。)在以下工作流中,将 APP_PEM 替换为机密的名称。 有关详细信息,请参阅“使用 GitHub Apps 进行身份验证”。

  6. 在以下工作流中,将 YOUR_ORGANIZATION 替换为组织的名称。 例如 octo-org。 将 YOUR_PROJECT_NUMBER 替换为项目编号。 要查找项目编号,请查看项目 URL。 例如,https://github.com/orgs/octo-org/projects/5 的项目编号为 5。

YAML
# 此工作流使用未经 GitHub 认证的操作。
# 它们由第三方提供,并受
# 单独的服务条款、隐私政策和支持
# 文档。

# GitHub 建议将操作固定到提交 SHA。
# 若要获取较新版本,需要更新 SHA。
# 还可以引用标记或分支,但该操作可能会更改而不发出警告。

name: Add PR to project
on:
  pull_request:
    types:
      - ready_for_review
jobs:
  track_pr:
    runs-on: ubuntu-latest
    steps:
      - name: Generate token
        id: generate_token
        uses: tibdex/github-app-token@36464acb844fc53b9b8b2401da68844f6b05ebb0
        with:
          app_id: ${{ secrets.APP_ID }}
          private_key: ${{ secrets.APP_PEM }}

      - name: Get project data
        env:
          GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
          ORGANIZATION: YOUR_ORGANIZATION
          PROJECT_NUMBER: YOUR_PROJECT_NUMBER
        run: |
          gh api graphql -f query='
            query($org: String!, $number: Int!) {
              organization(login: $org){
                projectV2(number: $number) {
                  id
                  fields(first:20) {
                    nodes {
                      ... on ProjectV2Field {
                        id
                        name
                      }
                      ... on ProjectV2SingleSelectField {
                        id
                        name
                        options {
                          id
                          name
                        }
                      }
                    }
                  }
                }
              }
            }' -f org=$ORGANIZATION -F number=$PROJECT_NUMBER > project_data.json

          echo 'PROJECT_ID='$(jq '.data.organization.projectV2.id' project_data.json) >> $GITHUB_ENV
          echo 'DATE_FIELD_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Date posted") | .id' project_data.json) >> $GITHUB_ENV
          echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV
          echo 'TODO_OPTION_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") | .options[] | select(.name=="Todo") |.id' project_data.json) >> $GITHUB_ENV

      - name: Add PR to project
        env:
          GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
          PR_ID: ${{ github.event.pull_request.node_id }}
        run: |
          item_id="$( gh api graphql -f query='
            mutation($project:ID!, $pr:ID!) {
              addProjectV2ItemById(input: {projectId: $project, contentId: $pr}) {
                item {
                  id
                }
              }
            }' -f project=$PROJECT_ID -f pr=$PR_ID --jq '.data.addProjectV2ItemById.item.id')"

            echo 'ITEM_ID='$item_id >> $GITHUB_ENV

      - name: Get date
        run: echo "DATE=$(date +"%Y-%m-%d")" >> $GITHUB_ENV

      - name: Set fields
        env:
          GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
        run: |
          gh api graphql -f query='
            mutation (
              $project: ID!
              $item: ID!
              $status_field: ID!
              $status_value: String!
              $date_field: ID!
              $date_value: Date!
            ) {
              set_status: updateProjectV2ItemFieldValue(input: {
                projectId: $project
                itemId: $item
                fieldId: $status_field
                value: { 
                  singleSelectOptionId: $status_value
                  }
              }) {
                projectV2Item {
                  id
                  }
              }
              set_date_posted: updateProjectV2ItemFieldValue(input: {
                projectId: $project
                itemId: $item
                fieldId: $date_field
                value: { 
                  date: $date_value
                }
              }) {
                projectV2Item {
                  id
                }
              }
            }' -f project=$PROJECT_ID -f item=$ITEM_ID -f status_field=$STATUS_FIELD_ID -f status_value=${{ env.TODO_OPTION_ID }} -f date_field=$DATE_FIELD_ID -f date_value=$DATE --silent

使用个人访问令牌进行身份验证的示例工作流程

  1. 使用 projectrepo 范围创建个人访问令牌。 有关详细信息,请参阅“创建个人访问令牌”。
  2. 将个人访问令牌另存为存储库或组织中的机密。
  3. 在以下工作流中,将 YOUR_TOKEN 替换为机密的名称。 将 YOUR_ORGANIZATION 替换为组织的名称。 例如,octo-org。 将 YOUR_PROJECT_NUMBER 替换为项目编号。 要查找项目编号,请查看项目 URL。 例如,https://github.com/orgs/octo-org/projects/5 的项目编号为 5。
YAML
name: Add PR to project
on:
  pull_request:
    types:
      - ready_for_review
jobs:
  track_pr:
    runs-on: ubuntu-latest
    steps:
      - name: Get project data
        env:
          GITHUB_TOKEN: ${{ secrets.YOUR_TOKEN }}
          ORGANIZATION: YOUR_ORGANIZATION
          PROJECT_NUMBER: YOUR_PROJECT_NUMBER
        run: |
          gh api graphql -f query='
            query($org: String!, $number: Int!) {
              organization(login: $org){
                projectV2(number: $number) {
                  id
                  fields(first:20) {
                    nodes {
                      ... on ProjectV2Field {
                        id
                        name
                      }
                      ... on ProjectV2SingleSelectField {
                        id
                        name
                        options {
                          id
                          name
                        }
                      }
                    }
                  }
                }
              }
            }' -f org=$ORGANIZATION -F number=$PROJECT_NUMBER > project_data.json

          echo 'PROJECT_ID='$(jq '.data.organization.projectV2.id' project_data.json) >> $GITHUB_ENV
          echo 'DATE_FIELD_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Date posted") | .id' project_data.json) >> $GITHUB_ENV
          echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV
          echo 'TODO_OPTION_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") | .options[] | select(.name=="Todo") |.id' project_data.json) >> $GITHUB_ENV

      - name: Add PR to project
        env:
          GITHUB_TOKEN: ${{ secrets.YOUR_TOKEN }}
          PR_ID: ${{ github.event.pull_request.node_id }}
        run: |
          item_id="$( gh api graphql -f query='
            mutation($project:ID!, $pr:ID!) {
              addProjectV2ItemById(input: {projectId: $project, contentId: $pr}) {
                item {
                  id
                }
              }
            }' -f project=$PROJECT_ID -f pr=$PR_ID --jq '.data.addProjectV2ItemById.item.id')"

            echo 'ITEM_ID='$item_id >> $GITHUB_ENV

      - name: Get date
        run: echo "DATE=$(date +"%Y-%m-%d")" >> $GITHUB_ENV

      - name: Set fields
        env:
          GITHUB_TOKEN: ${{ secrets.YOUR_TOKEN }}
        run: |
          gh api graphql -f query='
            mutation (
              $project: ID!
              $item: ID!
              $status_field: ID!
              $status_value: String!
              $date_field: ID!
              $date_value: Date!
            ) {
              set_status: updateProjectV2ItemFieldValue(input: {
                projectId: $project
                itemId: $item
                fieldId: $status_field
                value: { 
                  singleSelectOptionId: $status_value
                  }
              }) {
                projectV2Item {
                  id
                  }
              }
              set_date_posted: updateProjectV2ItemFieldValue(input: {
                projectId: $project
                itemId: $item
                fieldId: $date_field
                value: { 
                  date: $date_value
                }
              }) {
                projectV2Item {
                  id
                }
              }
            }' -f project=$PROJECT_ID -f item=$ITEM_ID -f status_field=$STATUS_FIELD_ID -f status_value=${{ env.TODO_OPTION_ID }} -f date_field=$DATE_FIELD_ID -f date_value=$DATE --silent

工作流程说明

下表说明了示例工作流程的各个部分,并向您展示了如何调整工作流程以供自己使用。

on:
  pull_request:
    types:
      - ready_for_review
当仓库中的拉取请求标记为“准备审核”时,此工作流程将运行。

仅限 GitHub App:

- name: Generate token
  id: generate_token
  uses: tibdex/github-app-token@36464acb844fc53b9b8b2401da68844f6b05ebb0
  with:
    app_id: ${{ secrets.APP_ID }}
    private_key: ${{ secrets.APP_PEM }}
使用 tibdex/github-app-token 操作从应用 ID 和私钥为应用生成安装访问令牌。 稍后在工作流中以 ${{ steps.generate_token.outputs.token }} 的形式访问安装访问令牌。

APP_ID 替换为包含应用 ID 的机密的名称。

APP_PEM 替换为包含应用私钥的机密的名称。

GitHub App:

env:
  GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
  ORGANIZATION: YOUR_ORGANIZATION
  PROJECT_NUMBER: YOUR_PROJECT_NUMBER

个人访问令牌:

env:
  GITHUB_TOKEN: ${{ secrets.YOUR_TOKEN }}
  ORGANIZATION: YOUR_ORGANIZATION
  PROJECT_NUMBER: YOUR_PROJECT_NUMBER
为此步骤设置环境变量。

如果使用个人访问令牌,请将 YOUR_TOKEN 替换为包含个人访问令牌的机密的名称。

YOUR_ORGANIZATION 替换为组织的名称。 例如,octo-org

YOUR_PROJECT_NUMBER 替换为项目编号。 要查找项目编号,请查看项目 URL。 例如,https://github.com/orgs/octo-org/projects/5 的项目编号为 5。
gh api graphql -f query='
  query($org: String!, $number: Int!) {
    organization(login: $org){
      projectV2(number: $number) {
        id
        fields(first:20) {
          nodes {
            ... on ProjectV2Field {
              id
              name
            }
            ... on ProjectV2SingleSelectField {
              id
              name
              options {
                id
                name
              }
            }
          }
        }
      }
    }
  }'  -f org=$ORGANIZATION -F number=$PROJECT_NUMBER > project_data.json

使用 GitHub CLI 查询项目的 ID 的 API,并返回项目中前 20 个字段的名称和 ID。 fields 会返回一个联合,查询使用内联片段 (... on) 返回任何 ProjectV2FieldProjectV2SingleSelectField 字段的相关信息。

响应存储在名为 project_data.json 的文件中。

echo 'PROJECT_ID='$(jq '.data.organization.projectV2.id' project_data.json) >> $GITHUB_ENV
echo 'DATE_FIELD_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Date posted") | .id' project_data.json) >> $GITHUB_ENV
echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV
echo 'TODO_OPTION_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") | .options[] | select(.name=="Todo") |.id' project_data.json) >> $GITHUB_ENV
解析 API 查询的响应,并将相关 ID 存储为环境变量。 修改此选项以获取不同字段或选项的 ID。 例如:
  • 若要获取名为 Team 的字段的 ID,请添加 echo 'TEAM_FIELD_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Team") | .id' project_data.json) >> $GITHUB_ENV
  • 若要获取 Team 单选字段的名为 Octoteam 的选项的 ID,请添加 echo 'OCTOTEAM_OPTION_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Team") |.options[] | select(.name=="Octoteam") |.id' project_data.json) >> $GITHUB_ENV
注意:此工作流程假定你有一个项目,其中包含一个名为“状态”的单选字段、一个名为“待办”的选项和一个名为“发布日期”的日期字段 。 您必须修改此部分以匹配表中存在的字段。

GitHub App:

env:
  GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
  PR_ID: ${{ github.event.pull_request.node_id }}

个人访问令牌:

env:
  GITHUB_TOKEN: ${{ secrets.YOUR_TOKEN }}
  PR_ID: ${{ github.event.pull_request.node_id }}
为此步骤设置环境变量。 GITHUB_TOKEN 如上所述。 PR_ID 是触发此工作流的拉取请求的 ID。
item_id="$( gh api graphql -f query='
  mutation($project:ID!, $pr:ID!) {
    addProjectV2ItemById(input: {projectId: $project, contentId: $pr}) {
      item {
        id
      }
    }
  }' -f project=$PROJECT_ID -f pr=$PR_ID --jq '.data.addProjectV2ItemById.item.id')"
使用 GitHub CLI 和 API 将触发此工作流的拉取请求添加到项目。 jq 标志解析响应以获取所创建项目的 ID。
echo 'ITEM_ID='$item_id >> $GITHUB_ENV
将已创建项的 ID 存储为环境变量。
echo "DATE=$(date +"%Y-%m-%d")" >> $GITHUB_ENV
yyyy-mm-dd 格式将当前日期保存为环境变量。

GitHub App:

env:
  GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}

个人访问令牌:

env:
  GITHUB_TOKEN: ${{ secrets.YOUR_TOKEN }}
为此步骤设置环境变量。 GITHUB_TOKEN 如上所述。
gh api graphql -f query='
  mutation (
    $project: ID!
    $item: ID!
    $status_field: ID!
    $status_value: String!
    $date_field: ID!
    $date_value: Date!
  ) {
    set_status: updateProjectV2ItemFieldValue(input: {
      projectId: $project
      itemId: $item
      fieldId: $status_field
      value: { 
        singleSelectOptionId: $status_value
        }
    }) {
      projectV2Item {
        id
        }
    }
    set_date_posted: updateProjectV2ItemFieldValue(input: {
      projectId: $project
      itemId: $item
      fieldId: $date_field
      value: { 
        date: $date_value
      }
    }) {
      projectV2Item {
        id
      }
    }
  }' -f project=$PROJECT_ID -f item=$ITEM_ID -f status_field=$STATUS_FIELD_ID -f status_value=${{ env.TODO_OPTION_ID }} -f date_field=$DATE_FIELD_ID -f date_value=$DATE --silent
Status 字段的值设置为 Todo。 设置 Date posted 字段的值。