Merge pull request #105 from lukewar/graphql/projectv2

Update Action to use new ProjectsV2 api
This commit is contained in:
Lukasz Warchol
2022-06-28 14:05:29 +02:00
committed by GitHub
10 changed files with 166 additions and 186 deletions

View File

@@ -1,5 +1,5 @@
--- ---
name: "@actions/core" name: '@actions/core'
version: 1.9.0 version: 1.9.0
type: npm type: npm
summary: Actions core lib summary: Actions core lib

View File

@@ -1,5 +1,5 @@
--- ---
name: "@actions/github" name: '@actions/github'
version: 5.0.3 version: 5.0.3
type: npm type: npm
summary: Actions github lib summary: Actions github lib

View File

@@ -1,5 +1,5 @@
--- ---
name: "@actions/http-client" name: '@actions/http-client'
version: 2.0.1 version: 2.0.1
type: npm type: npm
summary: Actions Http Client summary: Actions Http Client

View File

@@ -1,5 +1,5 @@
--- ---
name: "@octokit/core" name: '@octokit/core'
version: 3.6.0 version: 3.6.0
type: npm type: npm
summary: Extendable client for GitHub's REST & GraphQL APIs summary: Extendable client for GitHub's REST & GraphQL APIs
@@ -30,5 +30,5 @@ licenses:
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
- sources: README.md - sources: README.md
text: "[MIT](LICENSE)" text: '[MIT](LICENSE)'
notices: [] notices: []

View File

@@ -9,9 +9,9 @@ not the original GitHub projects.
[![build-test](https://github.com/actions/add-to-project/actions/workflows/test.yml/badge.svg)](https://github.com/actions/add-to-project/actions/workflows/test.yml) [![build-test](https://github.com/actions/add-to-project/actions/workflows/test.yml/badge.svg)](https://github.com/actions/add-to-project/actions/workflows/test.yml)
🚨 **This action is in beta, however the API is stable. Some breaking changes might occur between versions, but it is not likely to break as long as you use a specific SHA or version number** 🚨 > **NOTE:** This Action (currently) only supports auto-adding Issues/Pull Requests to a Project which lives in the same organization as your target Repository.
> **NOTE:** This Action (currently) only supports auto-adding Issues to a Project which lives in the same organization as your target Repository. > **NOTE:** This action no longer uses the deprecated ProjectNext API. If you are looking for the old version of that action, use version [v0.0.3](https://github.com/actions/add-to-project/releases/tag/v0.0.3).
## Usage ## Usage
@@ -23,7 +23,9 @@ Create a workflow that runs when Issues or Pull Requests are opened or labeled i
Once you've configured your workflow, save it as a `.yml` file in your target Repository's `.github/workflows` directory. Once you've configured your workflow, save it as a `.yml` file in your target Repository's `.github/workflows` directory.
##### Example Usage: Issue opened with labels `bug` OR `needs-triage` ### Examples
#### Example Usage: Issue opened with labels `bug` OR `needs-triage`
```yaml ```yaml
name: Add bugs to bugs project name: Add bugs to bugs project
@@ -38,7 +40,7 @@ jobs:
name: Add issue to project name: Add issue to project
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/add-to-project@main - uses: actions/add-to-project@RELEASE_VERSION
with: with:
project-url: https://github.com/orgs/<orgName>/projects/<projectNumber> project-url: https://github.com/orgs/<orgName>/projects/<projectNumber>
github-token: ${{ secrets.ADD_TO_PROJECT_PAT }} github-token: ${{ secrets.ADD_TO_PROJECT_PAT }}
@@ -46,7 +48,7 @@ jobs:
label-operator: OR label-operator: OR
``` ```
##### Example Usage: Adds all issues opened that do not include the label `bug` OR `needs-triage` #### Example Usage: Adds all issues opened that do not include the label `bug` OR `needs-triage`
```yaml ```yaml
name: Adds all issues that don't include the 'bug' or 'needs-triage' labels to project board name: Adds all issues that don't include the 'bug' or 'needs-triage' labels to project board
@@ -61,7 +63,7 @@ jobs:
name: Add issue to project name: Add issue to project
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/add-to-project@main - uses: actions/add-to-project@RELEASE_VERSION
with: with:
project-url: https://github.com/orgs/<orgName>/projects/<projectNumber> project-url: https://github.com/orgs/<orgName>/projects/<projectNumber>
github-token: ${{ secrets.ADD_TO_PROJECT_PAT }} github-token: ${{ secrets.ADD_TO_PROJECT_PAT }}
@@ -69,7 +71,7 @@ jobs:
label-operator: NOT label-operator: NOT
``` ```
##### Example Usage: Pull Requests labeled with `needs-review` and `size/XL` #### Example Usage: Pull Requests labeled with `needs-review` and `size/XL`
```yaml ```yaml
name: Add needs-review and size/XL pull requests to projects name: Add needs-review and size/XL pull requests to projects
@@ -84,7 +86,7 @@ jobs:
name: Add issue to project name: Add issue to project
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/add-to-project@main - uses: actions/add-to-project@RELEASE_VERSION
with: with:
project-url: https://github.com/orgs/<orgName>/projects/<projectNumber> project-url: https://github.com/orgs/<orgName>/projects/<projectNumber>
github-token: ${{ secrets.ADD_TO_PROJECT_PAT }} github-token: ${{ secrets.ADD_TO_PROJECT_PAT }}
@@ -92,22 +94,29 @@ jobs:
label-operator: AND label-operator: AND
``` ```
#### Further reading and additional resources ### Further reading and additional resources
- [actions/add-to-project](#actionsadd-to-project)
- [Current Status](#current-status)
- [Usage](#usage)
- [Examples](#examples)
- [Example Usage: Issue opened with labels `bug` OR `needs-triage`](#example-usage-issue-opened-with-labels-bug-or-needs-triage)
- [Example Usage: Adds all issues opened that do not include the label `bug` OR `needs-triage`](#example-usage-adds-all-issues-opened-that-do-not-include-the-label-bug-or-needs-triage)
- [Example Usage: Pull Requests labeled with `needs-review` and `size/XL`](#example-usage-pull-requests-labeled-with-needs-review-and-sizexl)
- [Further reading and additional resources](#further-reading-and-additional-resources)
- [Inputs](#inputs) - [Inputs](#inputs)
- [Supported Events](#supported-events) - [Supported Events](#supported-events)
- [How to point the action to a specific branch or commit sha](#how-to-point-the-action-to-a-specific-branch-or-commit-sha)
- [Creating a PAT and adding it to your repository](#creating-a-pat-and-adding-it-to-your-repository) - [Creating a PAT and adding it to your repository](#creating-a-pat-and-adding-it-to-your-repository)
- [Development](#development) - [Development](#development)
- [Publish to a distribution branch](#publish-to-a-distribution-branch) - [Publish to a distribution branch](#publish-to-a-distribution-branch)
- [License](#license)
## Inputs ## Inputs
- <a name="project-url">`project-url`</a> **(required)** is the URL of the GitHub project to add issues to. - <a name="project-url">`project-url`</a> **(required)** is the URL of the GitHub project to add issues to.
_eg: `https://github.com/orgs|users/<ownerName>/projects/<projectNumber>`_ _eg: `https://github.com/orgs|users/<ownerName>/projects/<projectNumber>`_
- <a name="github-token">`github-token`</a> **(required)** is a [personal access - <a name="github-token">`github-token`</a> **(required)** is a [personal access
token](https://github.com/settings/tokens/new) with the `repo`, `write:org` and token](https://github.com/settings/tokens/new) with `repo` and `project` scopes.
`read:org` scopes.
_See [Creating a PAT and adding it to your repository](#creating-a-pat-and-adding-it-to-your-repository) for more details_ _See [Creating a PAT and adding it to your repository](#creating-a-pat-and-adding-it-to-your-repository) for more details_
- <a name="labeled">`labeled`</a> **(optional)** is a comma-separated list of labels used to filter applicable issues. When this key is provided, an issue must have _one_ of the labels in the list to be added to the project. Omitting this key means that any issue will be added. - <a name="labeled">`labeled`</a> **(optional)** is a comma-separated list of labels used to filter applicable issues. When this key is provided, an issue must have _one_ of the labels in the list to be added to the project. Omitting this key means that any issue will be added.
- <a name="labeled">`label-operator`</a> **(optional)** is the behavior of the labels filter, either `AND`, `OR` or `NOT` that controls if the issue should be matched with `all` `labeled` input or any of them, default is `OR`. - <a name="labeled">`label-operator`</a> **(optional)** is the behavior of the labels filter, either `AND`, `OR` or `NOT` that controls if the issue should be matched with `all` `labeled` input or any of them, default is `OR`.
@@ -127,41 +136,10 @@ and the following [`pull_request` events](https://docs.github.com/en/actions/usi
Using these events ensure that a given issue or pull request, in the workflow's repo, is added to the [specified project](#project-url). If [labeled input(s)](#labeled) are defined, then issues will only be added if they contain at least _one_ of the labels in the list. Using these events ensure that a given issue or pull request, in the workflow's repo, is added to the [specified project](#project-url). If [labeled input(s)](#labeled) are defined, then issues will only be added if they contain at least _one_ of the labels in the list.
## How to point the action to a specific branch or commit sha
Pointing to a branch name generally isn't the safest way to refer to an action, but this is how you can use this action now before we've begun creating releases.
```yaml
jobs:
add-to-project:
name: Add issue to project
runs-on: ubuntu-latest
steps:
- uses: actions/add-to-project@main
with:
project-url: https://github.com/orgs/<orgName>/projects/<projectNumber>
github-token: ${{ secrets.ADD_TO_PROJECT_PAT }}
```
Another option would be to point to a full [commit SHA](https://docs.github.com/en/get-started/quickstart/github-glossary#commit):
```yaml
jobs:
add-to-project:
name: Add issue to project
runs-on: ubuntu-latest
steps:
- uses: actions/add-to-project@<commitSHA>
with:
project-url: https://github.com/orgs/<orgName>/projects/<projectNumber>
github-token: ${{ secrets.ADD_TO_PROJECT_PAT }}
```
## Creating a PAT and adding it to your repository ## Creating a PAT and adding it to your repository
- create a new [personal access - create a new [personal access
token](https://github.com/settings/tokens/new) with `repo`, `write:org` and token](https://github.com/settings/tokens/new) with `repo` and `project` scopes
`read:org` scopes
_See [Creating a personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) for more information_ _See [Creating a personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) for more information_
- add the newly created PAT as a repository secret, this secret will be referenced by the [github-token input](#github-token) - add the newly created PAT as a repository secret, this secret will be referenced by the [github-token input](#github-token)

View File

@@ -1,5 +1,6 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import * as github from '@actions/github' import * as github from '@actions/github'
import {addToProject, mustGetOwnerTypeQuery} from '../src/add-to-project' import {addToProject, mustGetOwnerTypeQuery} from '../src/add-to-project'
describe('addToProject', () => { describe('addToProject', () => {
@@ -29,18 +30,18 @@ describe('addToProject', () => {
test: /getProject/, test: /getProject/,
return: { return: {
organization: { organization: {
projectNext: { projectV2: {
id: 'project-next-id' id: 'project-id'
} }
} }
} }
}, },
{ {
test: /addProjectNextItem/, test: /addProjectV2ItemById/,
return: { return: {
addProjectNextItem: { addProjectV2ItemById: {
projectNextItem: { item: {
id: 'project-next-item-id' id: 'project-item-id'
} }
} }
} }
@@ -49,7 +50,7 @@ describe('addToProject', () => {
await addToProject() await addToProject()
expect(outputs.itemId).toEqual('project-next-item-id') expect(outputs.itemId).toEqual('project-item-id')
}) })
test('adds matching issues with a label filter without label-operator', async () => { test('adds matching issues with a label filter without label-operator', async () => {
@@ -71,18 +72,18 @@ describe('addToProject', () => {
test: /getProject/, test: /getProject/,
return: { return: {
organization: { organization: {
projectNext: { projectV2: {
id: 'project-next-id' id: 'project-id'
} }
} }
} }
}, },
{ {
test: /addProjectNextItem/, test: /addProjectV2ItemById/,
return: { return: {
addProjectNextItem: { addProjectV2ItemById: {
projectNextItem: { item: {
id: 'project-next-item-id' id: 'project-item-id'
} }
} }
} }
@@ -91,7 +92,7 @@ describe('addToProject', () => {
await addToProject() await addToProject()
expect(outputs.itemId).toEqual('project-next-item-id') expect(outputs.itemId).toEqual('project-item-id')
}) })
test('adds matching pull-requests with a label filter without label-operator', async () => { test('adds matching pull-requests with a label filter without label-operator', async () => {
@@ -114,18 +115,18 @@ describe('addToProject', () => {
test: /getProject/, test: /getProject/,
return: { return: {
organization: { organization: {
projectNext: { projectV2: {
id: 'project-next-id' id: 'project-id'
} }
} }
} }
}, },
{ {
test: /addProjectNextItem/, test: /addProjectV2ItemById/,
return: { return: {
addProjectNextItem: { addProjectV2ItemById: {
projectNextItem: { item: {
id: 'project-next-item-id' id: 'project-item-id'
} }
} }
} }
@@ -134,7 +135,7 @@ describe('addToProject', () => {
await addToProject() await addToProject()
expect(outputs.itemId).toEqual('project-next-item-id') expect(outputs.itemId).toEqual('project-item-id')
}) })
test('does not add un-matching issues with a label filter without label-operator', async () => { test('does not add un-matching issues with a label filter without label-operator', async () => {
@@ -178,18 +179,18 @@ describe('addToProject', () => {
test: /getProject/, test: /getProject/,
return: { return: {
organization: { organization: {
projectNext: { projectV2: {
id: 'project-next-id' id: 'project-id'
} }
} }
} }
}, },
{ {
test: /addProjectNextItem/, test: /addProjectV2ItemById/,
return: { return: {
addProjectNextItem: { addProjectV2ItemById: {
projectNextItem: { item: {
id: 'project-next-item-id' id: 'project-item-id'
} }
} }
} }
@@ -198,7 +199,7 @@ describe('addToProject', () => {
await addToProject() await addToProject()
expect(outputs.itemId).toEqual('project-next-item-id') expect(outputs.itemId).toEqual('project-item-id')
}) })
test('does not add un-matching issues with labels filter with AND label-operator', async () => { test('does not add un-matching issues with labels filter with AND label-operator', async () => {
@@ -307,18 +308,18 @@ describe('addToProject', () => {
test: /getProject/, test: /getProject/,
return: { return: {
organization: { organization: {
projectNext: { projectV2: {
id: 'project-next-id' id: 'project-id'
} }
} }
} }
}, },
{ {
test: /addProjectNextItem/, test: /addProjectV2ItemById/,
return: { return: {
addProjectNextItem: { addProjectV2ItemById: {
projectNextItem: { item: {
id: 'project-next-item-id' id: 'project-item-id'
} }
} }
} }
@@ -331,7 +332,7 @@ describe('addToProject', () => {
expect(gqlMock).toHaveBeenCalled() expect(gqlMock).toHaveBeenCalled()
expect(infoSpy).not.toHaveBeenCalled() expect(infoSpy).not.toHaveBeenCalled()
expect(outputs.itemId).toEqual('project-next-item-id') expect(outputs.itemId).toEqual('project-item-id')
}) })
test('does not add un-matching issues with multiple label filters', async () => { test('does not add un-matching issues with multiple label filters', async () => {
@@ -377,18 +378,18 @@ describe('addToProject', () => {
test: /getProject/, test: /getProject/,
return: { return: {
organization: { organization: {
projectNext: { projectV2: {
id: 'project-next-id' id: 'project-id'
} }
} }
} }
}, },
{ {
test: /addProjectNextItem/, test: /addProjectV2ItemById/,
return: { return: {
addProjectNextItem: { addProjectV2ItemById: {
projectNextItem: { item: {
id: 'project-next-item-id' id: 'project-item-id'
} }
} }
} }
@@ -401,7 +402,7 @@ describe('addToProject', () => {
expect(gqlMock).toHaveBeenCalled() expect(gqlMock).toHaveBeenCalled()
expect(infoSpy).not.toHaveBeenCalled() expect(infoSpy).not.toHaveBeenCalled()
expect(outputs.itemId).toEqual('project-next-item-id') expect(outputs.itemId).toEqual('project-item-id')
}) })
test(`throws an error when url isn't a valid project url`, async () => { test(`throws an error when url isn't a valid project url`, async () => {
@@ -467,18 +468,18 @@ describe('addToProject', () => {
test: /getProject/, test: /getProject/,
return: { return: {
organization: { organization: {
projectNext: { projectV2: {
id: 'project-next-id' id: 'project-id'
} }
} }
} }
}, },
{ {
test: /addProjectNextItem/, test: /addProjectV2ItemById/,
return: { return: {
addProjectNextItem: { addProjectV2ItemById: {
projectNextItem: { item: {
id: 'project-next-item-id' id: 'project-item-id'
} }
} }
} }
@@ -512,18 +513,18 @@ describe('addToProject', () => {
test: /getProject/, test: /getProject/,
return: { return: {
organization: { organization: {
projectNext: { projectV2: {
id: 'project-next-id' id: 'project-id'
} }
} }
} }
}, },
{ {
test: /addProjectNextItem/, test: /addProjectV2ItemById/,
return: { return: {
addProjectNextItem: { addProjectV2ItemById: {
projectNextItem: { item: {
id: 'project-next-item-id' id: 'project-item-id'
} }
} }
} }

16
dist/index.js generated vendored
View File

@@ -93,7 +93,7 @@ function addToProject() {
// First, use the GraphQL API to request the project's node ID. // First, use the GraphQL API to request the project's node ID.
const idResp = yield octokit.graphql(`query getProject($ownerName: String!, $projectNumber: Int!) { const idResp = yield octokit.graphql(`query getProject($ownerName: String!, $projectNumber: Int!) {
${ownerTypeQuery}(login: $ownerName) { ${ownerTypeQuery}(login: $ownerName) {
projectNext(number: $projectNumber) { projectV2(number: $projectNumber) {
id id
} }
} }
@@ -101,24 +101,24 @@ function addToProject() {
ownerName, ownerName,
projectNumber projectNumber
}); });
const projectId = (_h = idResp[ownerTypeQuery]) === null || _h === void 0 ? void 0 : _h.projectNext.id; const projectId = (_h = idResp[ownerTypeQuery]) === null || _h === void 0 ? void 0 : _h.projectV2.id;
const contentId = issue === null || issue === void 0 ? void 0 : issue.node_id; const contentId = issue === null || issue === void 0 ? void 0 : issue.node_id;
core.debug(`Project node ID: ${projectId}`); core.debug(`Project node ID: ${projectId}`);
core.debug(`Content ID: ${contentId}`); core.debug(`Content ID: ${contentId}`);
// Next, use the GraphQL API to add the issue to the project. // Next, use the GraphQL API to add the issue to the project.
const addResp = yield octokit.graphql(`mutation addIssueToProject($input: AddProjectNextItemInput!) { const addResp = yield octokit.graphql(`mutation addIssueToProject($input: AddProjectV2ItemByIdInput!) {
addProjectNextItem(input: $input) { addProjectV2ItemById(input: $input) {
projectNextItem { item {
id id
} }
} }
}`, { }`, {
input: { input: {
contentId, projectId,
projectId contentId
} }
}); });
core.setOutput('itemId', addResp.addProjectNextItem.projectNextItem.id); core.setOutput('itemId', addResp.addProjectV2ItemById.item.id);
}); });
} }
exports.addToProject = addToProject; exports.addToProject = addToProject;

2
dist/index.js.map generated vendored

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,6 @@
{ {
"name": "@actions/add-to-project", "name": "@actions/add-to-project",
"description": "Automatically issues and PRs to GitHub projects", "description": "Automatically adds issues and PRs to GitHub projects",
"version": "0.0.0", "version": "0.0.0",
"author": "GitHub and contributors", "author": "GitHub and contributors",
"dependencies": { "dependencies": {

View File

@@ -8,21 +8,21 @@ const urlParse =
interface ProjectNodeIDResponse { interface ProjectNodeIDResponse {
organization?: { organization?: {
projectNext: { projectV2: {
id: string id: string
} }
} }
user?: { user?: {
projectNext: { projectV2: {
id: string id: string
} }
} }
} }
interface ProjectAddItemResponse { interface ProjectAddItemResponse {
addProjectNextItem: { addProjectV2ItemById: {
projectNextItem: { item: {
id: string id: string
} }
} }
@@ -40,6 +40,7 @@ export async function addToProject(): Promise<void> {
const labelOperator = core.getInput('label-operator').trim().toLocaleLowerCase() const labelOperator = core.getInput('label-operator').trim().toLocaleLowerCase()
const octokit = github.getOctokit(ghToken) const octokit = github.getOctokit(ghToken)
const urlMatch = projectUrl.match(urlParse) const urlMatch = projectUrl.match(urlParse)
const issue = github.context.payload.issue ?? github.context.payload.pull_request const issue = github.context.payload.issue ?? github.context.payload.pull_request
const issueLabels: string[] = (issue?.labels ?? []).map((l: {name: string}) => l.name.toLowerCase()) const issueLabels: string[] = (issue?.labels ?? []).map((l: {name: string}) => l.name.toLowerCase())
@@ -83,7 +84,7 @@ export async function addToProject(): Promise<void> {
const idResp = await octokit.graphql<ProjectNodeIDResponse>( const idResp = await octokit.graphql<ProjectNodeIDResponse>(
`query getProject($ownerName: String!, $projectNumber: Int!) { `query getProject($ownerName: String!, $projectNumber: Int!) {
${ownerTypeQuery}(login: $ownerName) { ${ownerTypeQuery}(login: $ownerName) {
projectNext(number: $projectNumber) { projectV2(number: $projectNumber) {
id id
} }
} }
@@ -94,7 +95,7 @@ export async function addToProject(): Promise<void> {
} }
) )
const projectId = idResp[ownerTypeQuery]?.projectNext.id const projectId = idResp[ownerTypeQuery]?.projectV2.id
const contentId = issue?.node_id const contentId = issue?.node_id
core.debug(`Project node ID: ${projectId}`) core.debug(`Project node ID: ${projectId}`)
@@ -102,22 +103,22 @@ export async function addToProject(): Promise<void> {
// Next, use the GraphQL API to add the issue to the project. // Next, use the GraphQL API to add the issue to the project.
const addResp = await octokit.graphql<ProjectAddItemResponse>( const addResp = await octokit.graphql<ProjectAddItemResponse>(
`mutation addIssueToProject($input: AddProjectNextItemInput!) { `mutation addIssueToProject($input: AddProjectV2ItemByIdInput!) {
addProjectNextItem(input: $input) { addProjectV2ItemById(input: $input) {
projectNextItem { item {
id id
} }
} }
}`, }`,
{ {
input: { input: {
contentId, projectId,
projectId contentId
} }
} }
) )
core.setOutput('itemId', addResp.addProjectNextItem.projectNextItem.id) core.setOutput('itemId', addResp.addProjectV2ItemById.item.id)
} }
export function mustGetOwnerTypeQuery(ownerType?: string): 'organization' | 'user' { export function mustGetOwnerTypeQuery(ownerType?: string): 'organization' | 'user' {