Skip to content

Commit

Permalink
Merge pull request #73 from Jblew/main
Browse files Browse the repository at this point in the history
Support for ProjectsV2, closes #72
  • Loading branch information
imjohnbo authored Aug 30, 2022
2 parents 65bb8a8 + 52b0269 commit 46d9bc9
Show file tree
Hide file tree
Showing 6 changed files with 232 additions and 0 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Issue Bot is a flexible GitHub action that takes care of a few issue related tas
- Makes issue comments linking new and previous issues if `linked-comments` is true
- Assigns new issue only to the _next_ assignee in the list if `rotate-assignees` is true. Useful for duty rotation like first responder.
- Pairs well with [imjohnbo/extract-issue-template-fields](https://github.com/imjohnbo/extract-issue-template-fields) if you'd prefer to open issues based on [issue templates](https://docs.github.com/en/github/building-a-strong-community/about-issue-and-pull-request-templates#issue-templates)
- Adds new issue to user or organization project at `project-v2-path`

## v3 Migration
⚠️ If you're a `v2` user, please note that these breaking changes were introduced in `v3`: ⚠️
Expand Down Expand Up @@ -120,6 +121,16 @@ The linked comments (`linked-comments-new-issue-text`, `linked-comments-previous

- `newIssueNumber`: The new issue number.


## Projects support

Issue Bot currently supports [Projects](https://docs.github.com/en/issues/planning-and-tracking-with-projects/learning-about-projects/about-projects) (a.k.a. Projects v2, Projects Beta, and Projects Next) (`project-v2-path`) and [Projects (classic)](https://docs.github.com/en/issues/organizing-your-work-with-project-boards/managing-project-boards/about-project-boards) (`project`, `project-type`, `column`, and `milestone`). See [`action.yml`](action.yml) for more details about these inputs.

Except when adding an issue to a Projects (classic) repository board, where the [built in `github.token`'s](https://github.com/imjohnbo/issue-bot/blob/main/action.yml#L13) permissions suffice, it's recommended to use a GitHub App installation access token or personal access token with the proper scopes.

Support for Projects (classic) will be dropped in a future version.


## Contributing

Feel free to open an issue, or better yet, a
Expand Down
7 changes: 7 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ inputs:
Project number (not ID or name) to add issue to, e.g. 2.
required: false

project-v2-path:
description: |-
Path of the user or organization project, e.g. "users/{username}/projects/{number}" or "orgs/{name}/projects/{number}".
Unlike Projects (classic), Projects do not support repository projects.
Token with proper scopes required in "token" field. Personal access token or GitHub App installation access token. Read more here: https://docs.github.com/en/issues/planning-and-tracking-with-projects/automating-your-project/using-the-api-to-manage-projects#authentication.
required: false

column:
description: |-
Project column name to add issue to, e.g. To Do.
Expand Down
88 changes: 88 additions & 0 deletions dist/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 38 additions & 0 deletions docs/example-workflows/project-v2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#
# Daily standup, powered by imjohnbo/issue-bot
#
name: Daily Standup Projects V2
on:
schedule:
# Every day at noon
- cron: 0 12 * * *

jobs:
daily_standup:
name: Daily Standup Projects V2
runs-on: ubuntu-latest
steps:

- name: Today's date
run: echo "TODAY=$(date '+%Y-%m-%d')" >> $GITHUB_ENV

# Generates and pins new standup issue, closes previous, writes linking comments, and assigns to all assignees in list
- name: New standup issue
uses: imjohnbo/issue-bot@v3
with:
assignees: "octocat, monalisa"
labels: "standup"
title: Standup
body: |-
:wave: Hi, {{#each assignees}}@{{this}}{{#unless @last}}, {{/unless}}{{/each}}!
## Standup for ${{ env.TODAY }}
1. What did you work on yesterday?
2. What are you working on today?
3. What issues are blocking you?
pinned: true
close-previous: true
linked-comments: true
project-v2-path: users/jblew/projects/2
token: ${{ secrets.PAT }} # for example, a personal access token with `project` scope
1 change: 1 addition & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ try {
assignees: core.getInput('assignees'),
projectType: core.getInput('project-type'),
project: core.getInput('project'),
projectV2: core.getInput('project-v2-path'),
column: core.getInput('column'),
milestone: core.getInput('milestone'),
pinned: core.getInput('pinned') === 'true',
Expand Down
87 changes: 87 additions & 0 deletions lib/issue-bot.js
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,85 @@ const addIssueToProjectColumn = async (options) => {
});
};

const addIssueToProjectV2 = async (options) => {
core.info(`Adding issue with node ID ${options.issueNodeId} to project V2 URL: ${options.url}`);
const projectNodeId = await getProjectV2NodeIdFromUrl(options.url);
core.info(`Adding issue with node ID ${options.issueNodeId} to project V2 with node ID: ${projectNodeId}`);
const mutation = `
mutation {
addProjectV2ItemById(
input: {
projectId: "${projectNodeId}"
contentId: "${options.issueNodeId}"
}
) {
item {
id
}
}
}
`;

return octokit.graphql({
query: mutation,
headers: {
accept: 'application/vnd.github.elektra-preview+json'
}
});
};

const getProjectV2NodeIdFromUrl = async (url) => {
const match = /^.*(?<type>orgs|users)\/(?<name>[^/]+)\/projects\/(?<number>[0-9]+).*$/gm
.exec(url.trim());
if (!match || !match.groups || !match.groups.type || !match.groups.name || !match.groups.number) {
throw new Error('Malformed projectV2 url');
}
const { type, name, number } = match.groups;
return type === 'orgs'
? await getOrgProjectV2NodeId({ name, number })
: await getUserProjectV2NodeId({ name, number });
};

const getUserProjectV2NodeId = async (options) => {
const { name, number } = options;
const query = `
query FindUserProjectNodeID {
user(login: "${name}") {
projectV2(number: ${number}) {
id
}
}
}
`;
const data = await octokit.graphql({
query,
headers: {
accept: 'application/vnd.github.elektra-preview+json'
}
});
return data.user.projectV2.id;
};

const getOrgProjectV2NodeId = async (options) => {
const { name, number } = options;
const query = `
query FindOrgProjectNodeID {
organization(login: "${name}") {
projectV2(number: ${number}) {
id
}
}
}
`;
const data = await octokit.graphql({
query,
headers: {
accept: 'application/vnd.github.elektra-preview+json'
}
});
return data.organization.projectV2.id;
};

const addIssueToMilestone = async (issueNumber, milestoneNumber) => {
core.info(`Adding issue number ${issueNumber} to milestone number ${milestoneNumber}`);

Expand Down Expand Up @@ -335,6 +414,13 @@ const run = async (inputs) => {
});
}

if (inputs.projectV2) {
await addIssueToProjectV2({
issueNodeId: newIssueNodeId,
url: inputs.projectV2
});
}

if (inputs.milestone) {
await addIssueToMilestone(newIssueNumber, inputs.milestone);
}
Expand Down Expand Up @@ -394,5 +480,6 @@ exports.closeIssue = closeIssue;
exports.makeLinkedComments = makeLinkedComments;
exports.getPreviousIssue = getPreviousIssue;
exports.addIssueToProjectColumn = addIssueToProjectColumn;
exports.addIssueToProjectV2 = addIssueToProjectV2;
exports.addIssueToMilestone = addIssueToMilestone;
exports.run = run;

0 comments on commit 46d9bc9

Please sign in to comment.