Skip to content

Commit 915d0e7

Browse files
authored
Merge pull request #7 from m7kvqbe1/feat/default-column-label-remove
Return to default column on label removal
2 parents 3b9e95f + 7bfcfe8 commit 915d0e7

File tree

5 files changed

+204
-33
lines changed

5 files changed

+204
-33
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: Create Test Issue on PR
2+
3+
on:
4+
pull_request:
5+
types: [opened, synchronize, reopened]
6+
7+
jobs:
8+
create_test_issue:
9+
runs-on: ubuntu-latest
10+
steps:
11+
- name: Create test issue
12+
id: create_issue
13+
uses: actions/github-script@v7
14+
with:
15+
github-token: ${{ secrets.PAT_TOKEN }}
16+
script: |
17+
const issue = await github.rest.issues.create({
18+
owner: context.repo.owner,
19+
repo: context.repo.repo,
20+
title: 'Test issue for PR #' + context.issue.number,
21+
body: 'This is a test issue created to verify the issue mover script.',
22+
labels: ['Size: Small']
23+
});
24+
console.log('Test issue created:', issue.data.html_url);
25+
core.setOutput('issue_number', issue.data.number);
26+
27+
- name: Wait for move action
28+
run: sleep 30
29+
30+
- name: Clean up test issue
31+
if: always()
32+
uses: actions/github-script@v7
33+
with:
34+
github-token: ${{ secrets.PAT_TOKEN }}
35+
script: |
36+
await github.rest.issues.update({
37+
owner: context.repo.owner,
38+
repo: context.repo.repo,
39+
issue_number: ${{ steps.create_issue.outputs.issue_number }},
40+
state: 'closed'
41+
});
42+
console.log('Test issue closed');

.github/workflows/move_issue.yml

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
1-
name: Test Move Issue Action
1+
name: Move Labeled Issues
22

33
on:
4-
workflow_dispatch:
5-
inputs:
6-
branch:
7-
description: "Name of branch to test"
8-
required: true
9-
type: string
4+
issues:
5+
types: [labeled]
6+
7+
env:
8+
PAT_TOKEN: ${{ secrets.PAT_TOKEN }}
9+
PROJECT_URL: ${{ secrets.PROJECT_URL }}
1010

1111
jobs:
12-
test-move-issue:
12+
move_issues:
1313
runs-on: ubuntu-latest
1414
steps:
1515
- name: Move Issue to Project Column
16-
uses: m7kvqbe1/github-action-move-issues@${{ github.event.inputs.branch }}
16+
uses: m7kvqbe1/github-action-move-issues@main
1717
with:
1818
github-token: ${{ secrets.PAT_TOKEN }}
19-
project-url: ${{ secrets.PROJECT_URL }}
19+
project-url: ${{ env.PROJECT_URL }}
2020
target-labels: "Size: Small, Size: Medium"
2121
target-column: "Todo"
2222
ignored-columns: "In Progress, Done"
23+
default-column: "Candidates"

README.md

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,26 @@
11
# Move Issue to Project Column
22

3-
A GitHub Action to move issues between GitHub Projects V2 columns based on specific labels and criteria.
3+
A GitHub Action to move issues between GitHub Projects V2 columns based on specific labels and criteria. It can handle both labeling and unlabeling events.
44

55
## Inputs
66

7-
| Input | Description | Required |
8-
| ----------------- | -------------------------------------------------------------------------------------------------- | -------- |
9-
| `github-token` | Create a Personal Access Token (Classic) with the `public_repo` and `project` scopes. | Yes |
10-
| `project-url` | The URL of the GitHub Project V2. | Yes |
11-
| `target-labels` | Comma-separated list of labels that should trigger the action (e.g., "Size: Small, Size: Medium"). | Yes |
12-
| `target-column` | The target column name to move the issue to (e.g., "Candidates for Ready"). | Yes |
13-
| `ignored-columns` | Comma-separated list of column names to ignore (e.g., "Ready, In Progress, In Review, Done"). | Yes |
7+
| Input | Description | Required |
8+
| ----------------- | ------------------------------------------------------------------------------------------------------------------------ | -------- |
9+
| `github-token` | Create a Personal Access Token (Classic) with the `public_repo` and `project` scopes. | Yes |
10+
| `project-url` | The URL of the GitHub Project V2. | Yes |
11+
| `target-labels` | Comma-separated list of labels that should trigger the action (e.g., "Size: Small, Size: Medium"). | Yes |
12+
| `target-column` | The target column name to move the issue to when labeled (e.g., "Candidates for Ready"). | Yes |
13+
| `ignored-columns` | Comma-separated list of column names to ignore (e.g., "Ready, In Progress, In Review, Done"). | Yes |
14+
| `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 |
1415

1516
## Example Workflow
1617

1718
```yaml
18-
name: Move Issue on Label
19+
name: Move Issue on Label Change
1920

2021
on:
2122
issues:
22-
types: [labeled]
23+
types: [labeled, unlabeled]
2324

2425
jobs:
2526
move-issue:
@@ -33,6 +34,15 @@ jobs:
3334
target-labels: "Size: Small, Size: Medium"
3435
target-column: "Candidates for Ready"
3536
ignored-columns: "Ready, In Progress, In Review, Done"
37+
default-column: "To Do" # Optional: Remove this line if you don't want issues moved when labels are removed
3638
```
3739
3840
Get the latest `{release}` tag from https://github.com/m7kvqbe1/github-action-move-issues/releases.
41+
42+
## Behavior
43+
44+
- When an issue is labeled with one of the target labels, it will be moved to the specified target column.
45+
- When all target labels are removed from an issue:
46+
- If a default column is specified, the issue will be moved to that column.
47+
- If no default column is specified, no action will be taken.
48+
- The action will not move issues that are already in one of the ignored columns.

action.yml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
name: "Move Issue to Project Column"
2-
description: "A GitHub Action to move issues between GitHub Projects V2 columns based on specific labels and criteria."
1+
name: "Move Issue in Project"
2+
description: "A GitHub Action to move issues between GitHub Projects V2 columns based on labeling and unlabeling events."
33
author: "m7kvqbe1"
44

55
inputs:
@@ -13,11 +13,14 @@ inputs:
1313
description: 'Comma-separated list of labels that should trigger the action (e.g., "Size: Small, Size: Medium")'
1414
required: true
1515
target-column:
16-
description: 'The target column name to move the issue to (e.g., "Candidates for Ready")'
16+
description: 'The target column name to move the issue to when labeled (e.g., "Candidates for Ready")'
1717
required: true
1818
ignored-columns:
1919
description: 'Comma-separated list of column names to ignore (e.g., "Ready, In Progress, In Review, Done")'
2020
required: true
21+
default-column:
22+
description: 'The column to move the issue to when a target label is removed. If not specified, no action will be taken on unlabeling.'
23+
required: false
2124

2225
runs:
2326
using: "node20"

index.js

Lines changed: 125 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -258,34 +258,149 @@ const processIssueItem = async (
258258
console.log(`Moved issue #${issue.number} to "${TARGET_COLUMN}"`);
259259
};
260260

261+
const handleLabeledEvent = async (
262+
octokit,
263+
issue,
264+
projectData,
265+
TARGET_COLUMN,
266+
IGNORED_COLUMNS,
267+
TARGET_LABELS
268+
) => {
269+
validateIssue(issue, TARGET_LABELS);
270+
271+
await processIssueItem(
272+
octokit,
273+
projectData,
274+
issue,
275+
TARGET_COLUMN,
276+
IGNORED_COLUMNS
277+
);
278+
};
279+
280+
const handleUnlabeledEvent = async (
281+
octokit,
282+
issue,
283+
projectData,
284+
DEFAULT_COLUMN,
285+
IGNORED_COLUMNS,
286+
TARGET_LABELS
287+
) => {
288+
const removedLabel = github.context.payload.label.name;
289+
if (!TARGET_LABELS.includes(removedLabel)) {
290+
return;
291+
}
292+
293+
const hasTargetLabel = issue.labels.some((label) =>
294+
TARGET_LABELS.includes(label.name)
295+
);
296+
297+
if (hasTargetLabel) {
298+
console.log(
299+
`Issue #${issue.number} still has a target label. Not moving to default column.`
300+
);
301+
return;
302+
}
303+
304+
await moveIssueToDefaultColumn(
305+
octokit,
306+
projectData,
307+
issue,
308+
DEFAULT_COLUMN,
309+
IGNORED_COLUMNS
310+
);
311+
};
312+
313+
const moveIssueToDefaultColumn = async (
314+
octokit,
315+
projectData,
316+
issue,
317+
defaultColumn,
318+
ignoredColumns
319+
) => {
320+
const statusField = await getStatusField(octokit, projectData.id);
321+
const defaultStatusOption = getTargetStatusOption(statusField, defaultColumn);
322+
323+
if (!defaultStatusOption) {
324+
throw new Error(`Default column "${defaultColumn}" not found in project`);
325+
}
326+
327+
let issueItemData = await getIssueItemData(
328+
octokit,
329+
projectData.id,
330+
issue.node_id
331+
);
332+
333+
if (!issueItemData) {
334+
console.log(`Issue #${issue.number} is not in the project. Skipping.`);
335+
return;
336+
}
337+
338+
const currentStatus = getCurrentStatus(issueItemData);
339+
340+
if (ignoredColumns.includes(currentStatus)) {
341+
console.log(
342+
`Issue #${issue.number} is in an ignored column (${currentStatus}). Skipping.`
343+
);
344+
return;
345+
}
346+
347+
await updateIssueStatus(
348+
octokit,
349+
projectData.id,
350+
issueItemData.id,
351+
statusField.id,
352+
defaultStatusOption.id
353+
);
354+
console.log(`Moved issue #${issue.number} back to "${defaultColumn}"`);
355+
};
356+
261357
const run = async () => {
262358
try {
263359
const token = core.getInput("github-token");
264360
const projectUrl = core.getInput("project-url");
265361
const targetLabels = core.getInput("target-labels");
266362
const targetColumn = core.getInput("target-column");
267363
const ignoredColumns = core.getInput("ignored-columns");
364+
const defaultColumn = core.getInput("default-column", { required: false });
268365

269366
const TARGET_COLUMN = targetColumn.trim();
270367
const TARGET_LABELS = parseCommaSeparatedInput(targetLabels);
271368
const IGNORED_COLUMNS = parseCommaSeparatedInput(ignoredColumns);
369+
const DEFAULT_COLUMN = defaultColumn ? defaultColumn.trim() : null;
272370

273371
const octokit = github.getOctokit(token);
274372
const issue = github.context.payload.issue;
275-
276-
validateIssue(issue, TARGET_LABELS);
373+
const action = github.context.payload.action;
277374

278375
const projectData = await getProjectData(octokit, projectUrl);
279376

280-
await processIssueItem(
281-
octokit,
282-
projectData,
283-
issue,
284-
TARGET_COLUMN,
285-
IGNORED_COLUMNS
286-
);
377+
if (action === "labeled") {
378+
await handleLabeledEvent(
379+
octokit,
380+
issue,
381+
projectData,
382+
TARGET_COLUMN,
383+
IGNORED_COLUMNS,
384+
TARGET_LABELS
385+
);
386+
return;
387+
}
388+
389+
if (action === "unlabeled" && DEFAULT_COLUMN) {
390+
await handleUnlabeledEvent(
391+
octokit,
392+
issue,
393+
projectData,
394+
DEFAULT_COLUMN,
395+
IGNORED_COLUMNS,
396+
TARGET_LABELS
397+
);
398+
return;
399+
}
400+
401+
console.log(`No action taken for ${action} event.`);
287402
} catch (error) {
288-
core.setFailed(`Error moving issue: ${error.message}`);
403+
core.setFailed(`Error processing issue: ${error.message}`);
289404
}
290405
};
291406

0 commit comments

Comments
 (0)