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
type: npm
summary: Actions core lib

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
---
name: "@octokit/core"
name: '@octokit/core'
version: 3.6.0
type: npm
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
THE SOFTWARE.
- sources: README.md
text: "[MIT](LICENSE)"
text: '[MIT](LICENSE)'
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)
🚨 **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
@@ -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.
##### Example Usage: Issue opened with labels `bug` OR `needs-triage`
### Examples
#### Example Usage: Issue opened with labels `bug` OR `needs-triage`
```yaml
name: Add bugs to bugs project
@@ -38,7 +40,7 @@ jobs:
name: Add issue to project
runs-on: ubuntu-latest
steps:
- uses: actions/add-to-project@main
- uses: actions/add-to-project@RELEASE_VERSION
with:
project-url: https://github.com/orgs/<orgName>/projects/<projectNumber>
github-token: ${{ secrets.ADD_TO_PROJECT_PAT }}
@@ -46,7 +48,7 @@ jobs:
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
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
runs-on: ubuntu-latest
steps:
- uses: actions/add-to-project@main
- uses: actions/add-to-project@RELEASE_VERSION
with:
project-url: https://github.com/orgs/<orgName>/projects/<projectNumber>
github-token: ${{ secrets.ADD_TO_PROJECT_PAT }}
@@ -69,7 +71,7 @@ jobs:
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
name: Add needs-review and size/XL pull requests to projects
@@ -84,7 +86,7 @@ jobs:
name: Add issue to project
runs-on: ubuntu-latest
steps:
- uses: actions/add-to-project@main
- uses: actions/add-to-project@RELEASE_VERSION
with:
project-url: https://github.com/orgs/<orgName>/projects/<projectNumber>
github-token: ${{ secrets.ADD_TO_PROJECT_PAT }}
@@ -92,22 +94,29 @@ jobs:
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)
- [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)
- [Development](#development)
- [Publish to a distribution branch](#publish-to-a-distribution-branch)
- [License](#license)
## Inputs
- <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>`_
- <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
`read:org` scopes.
token](https://github.com/settings/tokens/new) with `repo` and `project` scopes.
_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">`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.
## 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
- create a new [personal access
token](https://github.com/settings/tokens/new) with `repo`, `write:org` and
`read:org` scopes
token](https://github.com/settings/tokens/new) with `repo` and `project` 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_
- 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 github from '@actions/github'
import {addToProject, mustGetOwnerTypeQuery} from '../src/add-to-project'
describe('addToProject', () => {
@@ -29,18 +30,18 @@ describe('addToProject', () => {
test: /getProject/,
return: {
organization: {
projectNext: {
id: 'project-next-id'
projectV2: {
id: 'project-id'
}
}
}
},
{
test: /addProjectNextItem/,
test: /addProjectV2ItemById/,
return: {
addProjectNextItem: {
projectNextItem: {
id: 'project-next-item-id'
addProjectV2ItemById: {
item: {
id: 'project-item-id'
}
}
}
@@ -49,7 +50,7 @@ describe('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 () => {
@@ -71,18 +72,18 @@ describe('addToProject', () => {
test: /getProject/,
return: {
organization: {
projectNext: {
id: 'project-next-id'
projectV2: {
id: 'project-id'
}
}
}
},
{
test: /addProjectNextItem/,
test: /addProjectV2ItemById/,
return: {
addProjectNextItem: {
projectNextItem: {
id: 'project-next-item-id'
addProjectV2ItemById: {
item: {
id: 'project-item-id'
}
}
}
@@ -91,7 +92,7 @@ describe('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 () => {
@@ -114,18 +115,18 @@ describe('addToProject', () => {
test: /getProject/,
return: {
organization: {
projectNext: {
id: 'project-next-id'
projectV2: {
id: 'project-id'
}
}
}
},
{
test: /addProjectNextItem/,
test: /addProjectV2ItemById/,
return: {
addProjectNextItem: {
projectNextItem: {
id: 'project-next-item-id'
addProjectV2ItemById: {
item: {
id: 'project-item-id'
}
}
}
@@ -134,7 +135,7 @@ describe('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 () => {
@@ -178,18 +179,18 @@ describe('addToProject', () => {
test: /getProject/,
return: {
organization: {
projectNext: {
id: 'project-next-id'
projectV2: {
id: 'project-id'
}
}
}
},
{
test: /addProjectNextItem/,
test: /addProjectV2ItemById/,
return: {
addProjectNextItem: {
projectNextItem: {
id: 'project-next-item-id'
addProjectV2ItemById: {
item: {
id: 'project-item-id'
}
}
}
@@ -198,7 +199,7 @@ describe('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 () => {
@@ -307,18 +308,18 @@ describe('addToProject', () => {
test: /getProject/,
return: {
organization: {
projectNext: {
id: 'project-next-id'
projectV2: {
id: 'project-id'
}
}
}
},
{
test: /addProjectNextItem/,
test: /addProjectV2ItemById/,
return: {
addProjectNextItem: {
projectNextItem: {
id: 'project-next-item-id'
addProjectV2ItemById: {
item: {
id: 'project-item-id'
}
}
}
@@ -331,7 +332,7 @@ describe('addToProject', () => {
expect(gqlMock).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 () => {
@@ -377,18 +378,18 @@ describe('addToProject', () => {
test: /getProject/,
return: {
organization: {
projectNext: {
id: 'project-next-id'
projectV2: {
id: 'project-id'
}
}
}
},
{
test: /addProjectNextItem/,
test: /addProjectV2ItemById/,
return: {
addProjectNextItem: {
projectNextItem: {
id: 'project-next-item-id'
addProjectV2ItemById: {
item: {
id: 'project-item-id'
}
}
}
@@ -401,7 +402,7 @@ describe('addToProject', () => {
expect(gqlMock).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 () => {
@@ -467,18 +468,18 @@ describe('addToProject', () => {
test: /getProject/,
return: {
organization: {
projectNext: {
id: 'project-next-id'
projectV2: {
id: 'project-id'
}
}
}
},
{
test: /addProjectNextItem/,
test: /addProjectV2ItemById/,
return: {
addProjectNextItem: {
projectNextItem: {
id: 'project-next-item-id'
addProjectV2ItemById: {
item: {
id: 'project-item-id'
}
}
}
@@ -512,18 +513,18 @@ describe('addToProject', () => {
test: /getProject/,
return: {
organization: {
projectNext: {
id: 'project-next-id'
projectV2: {
id: 'project-id'
}
}
}
},
{
test: /addProjectNextItem/,
test: /addProjectV2ItemById/,
return: {
addProjectNextItem: {
projectNextItem: {
id: 'project-next-item-id'
addProjectV2ItemById: {
item: {
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.
const idResp = yield octokit.graphql(`query getProject($ownerName: String!, $projectNumber: Int!) {
${ownerTypeQuery}(login: $ownerName) {
projectNext(number: $projectNumber) {
projectV2(number: $projectNumber) {
id
}
}
@@ -101,24 +101,24 @@ function addToProject() {
ownerName,
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;
core.debug(`Project node ID: ${projectId}`);
core.debug(`Content ID: ${contentId}`);
// Next, use the GraphQL API to add the issue to the project.
const addResp = yield octokit.graphql(`mutation addIssueToProject($input: AddProjectNextItemInput!) {
addProjectNextItem(input: $input) {
projectNextItem {
const addResp = yield octokit.graphql(`mutation addIssueToProject($input: AddProjectV2ItemByIdInput!) {
addProjectV2ItemById(input: $input) {
item {
id
}
}
}`, {
input: {
contentId,
projectId
projectId,
contentId
}
});
core.setOutput('itemId', addResp.addProjectNextItem.projectNextItem.id);
core.setOutput('itemId', addResp.addProjectV2ItemById.item.id);
});
}
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",
"description": "Automatically issues and PRs to GitHub projects",
"description": "Automatically adds issues and PRs to GitHub projects",
"version": "0.0.0",
"author": "GitHub and contributors",
"dependencies": {

View File

@@ -8,21 +8,21 @@ const urlParse =
interface ProjectNodeIDResponse {
organization?: {
projectNext: {
projectV2: {
id: string
}
}
user?: {
projectNext: {
projectV2: {
id: string
}
}
}
interface ProjectAddItemResponse {
addProjectNextItem: {
projectNextItem: {
addProjectV2ItemById: {
item: {
id: string
}
}
@@ -40,6 +40,7 @@ export async function addToProject(): Promise<void> {
const labelOperator = core.getInput('label-operator').trim().toLocaleLowerCase()
const octokit = github.getOctokit(ghToken)
const urlMatch = projectUrl.match(urlParse)
const issue = github.context.payload.issue ?? github.context.payload.pull_request
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>(
`query getProject($ownerName: String!, $projectNumber: Int!) {
${ownerTypeQuery}(login: $ownerName) {
projectNext(number: $projectNumber) {
projectV2(number: $projectNumber) {
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
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.
const addResp = await octokit.graphql<ProjectAddItemResponse>(
`mutation addIssueToProject($input: AddProjectNextItemInput!) {
addProjectNextItem(input: $input) {
projectNextItem {
`mutation addIssueToProject($input: AddProjectV2ItemByIdInput!) {
addProjectV2ItemById(input: $input) {
item {
id
}
}
}`,
{
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' {