Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ A GitHub Action to move issues between GitHub Projects V2 columns based on speci
| `ignored-columns` | Comma-separated list of column names to ignore (e.g., "Ready, In Progress, In Review, Done"). | Yes |
| `default-column` | The column to move the issue to when a target label is removed. If not specified, no action will be taken on unlabeling. | No |
| `skip-if-not-in-project` | Skip moving the issue if it is not already in the project (default: `false`) | No |
| `issue-number` | Issue number to handle (default: derived from issue itself) | No |

## Example Workflow

Expand Down Expand Up @@ -47,3 +48,48 @@ Get the latest `{release}` tag from https://github.com/m7kvqbe1/github-action-mo
- If a default column is specified, the issue will be moved to that column.
- If no default column is specified, no action will be taken.
- The action will not move issues that are already in one of the ignored columns.

## Advanced Behavior

When a pull request is closed, one might want to move the issue, too.
Then, the issue number has to be determined based on the pull request.
For instance, this can be done with the [ticket-check-action](https://github.com/neofinancial/ticket-check-action/pull/58).
Then, one can move the issue to another state as intended.

Example: Move the issue to "In Progress" as soon as a PR is opened or reopened:

```yaml
name: Mark issue as in progress

on:
pull_request_target:

jobs:
move_issue:
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- name: Determine issue number
id: get_issue_number
uses: koppor/ticket-check-action@add-output
with:
token: ${{ secrets.GITHUB_TOKEN }}
ticketLink: 'https://github.com/:owner/:repo/issues/%ticketNumber%'
ticketPrefix: '#'
titleRegex: '^#(?<ticketNumber>\d+)'
branchRegex: '^(?<ticketNumber>\d+)'
bodyRegex: '#(?<ticketNumber>\d+)'
bodyURLRegex: 'http(s?):\/\/(github.com)(\/:owner)(\/:repo)(\/issues)\/(?<ticketNumber>\d+)'
outputOnly: true
- name: Move issue "In progress"
uses: m7kvqbe1/github-action-move-issues/@main
with:
github-token: ${{ secrets.GH_TOKEN_ACTION_MOVE_ISSUE }}
project-url: "https://github.com/users/koppor/projects/7"
target-labels: "📍 Assigned"
target-column: "In progress"
ignored-columns: ""
default-column: "In progress"
issue-number: ${{ steps.get_issue_number.outputs.ticketNumber }}
```
3 changes: 3 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ inputs:
default-column:
description: 'The column to move the issue to when a target label is removed. If not specified, no action will be taken on unlabeling.'
required: false
issue-number:
description: 'The issue number to use. Useful for triggering on PRs'
required: false
skip-if-not-in-project:
description: 'If set to "true", skip moving the issue if it is not already in the project.'
required: false
Expand Down
78 changes: 45 additions & 33 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -34913,67 +34913,77 @@ const run = async () => {
const targetColumn = _actions_core__WEBPACK_IMPORTED_MODULE_0__.getInput("target-column");
const ignoredColumns = _actions_core__WEBPACK_IMPORTED_MODULE_0__.getInput("ignored-columns");
const defaultColumn = _actions_core__WEBPACK_IMPORTED_MODULE_0__.getInput("default-column", { required: false });
const issueNumberParam = _actions_core__WEBPACK_IMPORTED_MODULE_0__.getInput("issue-number", { required: false });

const TARGET_COLUMN = targetColumn.trim();
const TARGET_LABELS = parseCommaSeparatedInput(targetLabels);
const IGNORED_COLUMNS = parseCommaSeparatedInput(ignoredColumns);
const DEFAULT_COLUMN = defaultColumn ? defaultColumn.trim() : null;
const ISSUE_NUMBER_PARAM = issueNumberParam ? issueNumberParam.trim() : null;

const SKIP_IF_NOT_IN_PROJECT =
_actions_core__WEBPACK_IMPORTED_MODULE_0__.getInput("skip-if-not-in-project") === "true";

const octokit = _actions_github__WEBPACK_IMPORTED_MODULE_1__.getOctokit(token);

let issue = _actions_github__WEBPACK_IMPORTED_MODULE_1__.context.payload.issue;
if (!issue || !issue.node_id) {
throw new Error("Invalid or missing issue object");
}
_actions_core__WEBPACK_IMPORTED_MODULE_0__.debug("Determing issue number...");
const issueNumber = ISSUE_NUMBER_PARAM ? ISSUE_NUMBER_PARAM : _actions_github__WEBPACK_IMPORTED_MODULE_1__.context.issue.number;
_actions_core__WEBPACK_IMPORTED_MODULE_0__.debug("Issue number: " + issueNumber);

const issueNumber = _actions_github__WEBPACK_IMPORTED_MODULE_1__.context.issue.number;
const { owner, repo } = _actions_github__WEBPACK_IMPORTED_MODULE_1__.context.repo;
// The issue might have been updated by a previous GitHub action; therefore, we refetch the issue data
const { owner, repo } = _actions_github__WEBPACK_IMPORTED_MODULE_1__.context.repo;
_actions_core__WEBPACK_IMPORTED_MODULE_0__.debug("owner: " + owner + " repo: " + repo);
const { data: updatedIssue } = await octokit.rest.issues.get({
owner,
repo,
issue_number: issueNumber,
});
issue = updatedIssue;
_actions_core__WEBPACK_IMPORTED_MODULE_0__.debug("Udpated issue " + updatedIssue);
const issue = updatedIssue;
_actions_core__WEBPACK_IMPORTED_MODULE_0__.debug("Issue " + issue);

if (!issue || !issue.node_id) {
throw new Error("Invalid or missing issue object");
}

const eventName = _actions_github__WEBPACK_IMPORTED_MODULE_1__.context.eventName;
const action = _actions_github__WEBPACK_IMPORTED_MODULE_1__.context.payload.action;
const projectData = await getProjectData(octokit, projectUrl);

if (eventName === "issue_comment") {
if (action === "created") {
// a comment was created on an issue
const hasTargetLabel = issue.labels.some((label) =>
TARGET_LABELS.includes(label.name)
_actions_core__WEBPACK_IMPORTED_MODULE_0__.debug("Event: " + eventName);

if ((eventName === "issue_comment") || (eventName === "pull_request") || (eventName === "pull_request_target") || ISSUE_NUMBER_PARAM) {
_actions_core__WEBPACK_IMPORTED_MODULE_0__.debug("Hit issue or PR");

const hasTargetLabel = issue.labels.some((label) =>
TARGET_LABELS.includes(label.name)
);
if (hasTargetLabel) {
// Proceed as if the label was added to the issue
await processIssueItem(
octokit,
projectData,
issue,
TARGET_COLUMN,
IGNORED_COLUMNS,
SKIP_IF_NOT_IN_PROJECT
);
} else {
// Proceed as if the label was removed from the issue
await moveIssueToDefaultColumn(
octokit,
projectData,
issue,
DEFAULT_COLUMN,
IGNORED_COLUMNS
);
if (hasTargetLabel) {
// Proceed as if the label was added to the issue
await processIssueItem(
octokit,
projectData,
issue,
TARGET_COLUMN,
IGNORED_COLUMNS,
SKIP_IF_NOT_IN_PROJECT
);
} else {
// Proceed as if the label was removed from the issue
await moveIssueToDefaultColumn(
octokit,
projectData,
issue,
DEFAULT_COLUMN,
IGNORED_COLUMNS
);
}
return;
}
return;
}

if (action === "labeled") {
_actions_core__WEBPACK_IMPORTED_MODULE_0__.debug("Hit labeled");

await handleLabeledEvent(
octokit,
issue,
Expand All @@ -34987,6 +34997,8 @@ const run = async () => {
}

if (action === "unlabeled" && DEFAULT_COLUMN) {
_actions_core__WEBPACK_IMPORTED_MODULE_0__.debug("Hit unlabeled");

await handleUnlabeledEvent(
octokit,
issue,
Expand Down
78 changes: 45 additions & 33 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -391,67 +391,77 @@ const run = async () => {
const targetColumn = core.getInput("target-column");
const ignoredColumns = core.getInput("ignored-columns");
const defaultColumn = core.getInput("default-column", { required: false });
const issueNumberParam = core.getInput("issue-number", { required: false });

const TARGET_COLUMN = targetColumn.trim();
const TARGET_LABELS = parseCommaSeparatedInput(targetLabels);
const IGNORED_COLUMNS = parseCommaSeparatedInput(ignoredColumns);
const DEFAULT_COLUMN = defaultColumn ? defaultColumn.trim() : null;
const ISSUE_NUMBER_PARAM = issueNumberParam ? issueNumberParam.trim() : null;

const SKIP_IF_NOT_IN_PROJECT =
core.getInput("skip-if-not-in-project") === "true";

const octokit = github.getOctokit(token);

let issue = github.context.payload.issue;
if (!issue || !issue.node_id) {
throw new Error("Invalid or missing issue object");
}
core.debug("Determing issue number...");
const issueNumber = ISSUE_NUMBER_PARAM ? ISSUE_NUMBER_PARAM : github.context.issue.number;
core.debug("Issue number: " + issueNumber);

const issueNumber = github.context.issue.number;
const { owner, repo } = github.context.repo;
// The issue might have been updated by a previous GitHub action; therefore, we refetch the issue data
const { owner, repo } = github.context.repo;
core.debug("owner: " + owner + " repo: " + repo);
const { data: updatedIssue } = await octokit.rest.issues.get({
owner,
repo,
issue_number: issueNumber,
});
issue = updatedIssue;
core.debug("Udpated issue " + updatedIssue);
const issue = updatedIssue;
core.debug("Issue " + issue);

if (!issue || !issue.node_id) {
throw new Error("Invalid or missing issue object");
}

const eventName = github.context.eventName;
const action = github.context.payload.action;
const projectData = await getProjectData(octokit, projectUrl);

if (eventName === "issue_comment") {
if (action === "created") {
// a comment was created on an issue
const hasTargetLabel = issue.labels.some((label) =>
TARGET_LABELS.includes(label.name)
core.debug("Event: " + eventName);

if ((eventName === "issue_comment") || (eventName === "pull_request") || (eventName === "pull_request_target") || ISSUE_NUMBER_PARAM) {
core.debug("Hit issue or PR");

const hasTargetLabel = issue.labels.some((label) =>
TARGET_LABELS.includes(label.name)
);
if (hasTargetLabel) {
// Proceed as if the label was added to the issue
await processIssueItem(
octokit,
projectData,
issue,
TARGET_COLUMN,
IGNORED_COLUMNS,
SKIP_IF_NOT_IN_PROJECT
);
} else {
// Proceed as if the label was removed from the issue
await moveIssueToDefaultColumn(
octokit,
projectData,
issue,
DEFAULT_COLUMN,
IGNORED_COLUMNS
);
if (hasTargetLabel) {
// Proceed as if the label was added to the issue
await processIssueItem(
octokit,
projectData,
issue,
TARGET_COLUMN,
IGNORED_COLUMNS,
SKIP_IF_NOT_IN_PROJECT
);
} else {
// Proceed as if the label was removed from the issue
await moveIssueToDefaultColumn(
octokit,
projectData,
issue,
DEFAULT_COLUMN,
IGNORED_COLUMNS
);
}
return;
}
return;
}

if (action === "labeled") {
core.debug("Hit labeled");

await handleLabeledEvent(
octokit,
issue,
Expand All @@ -465,6 +475,8 @@ const run = async () => {
}

if (action === "unlabeled" && DEFAULT_COLUMN) {
core.debug("Hit unlabeled");

await handleUnlabeledEvent(
octokit,
issue,
Expand Down