Skip to content

Commit

Permalink
archive tickets
Browse files Browse the repository at this point in the history
  • Loading branch information
TimVanMourik committed May 14, 2024
1 parent 7a48df2 commit 96d4d75
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 153 deletions.
71 changes: 21 additions & 50 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,86 +1,57 @@
# Move to next iteration
# Archive tickets from a certain column for an iteration

Automatically move issues and pull requests to the next iteration of your [GitHub project](https://docs.github.com/en/issues/planning-and-tracking-with-projects/learning-about-projects/about-projects) with this [Github Action](https://github.com/features/actions).
Automatically archive issues for a given iteration of your [GitHub project](https://docs.github.com/en/issues/planning-and-tracking-with-projects/learning-about-projects/about-projects) with this [Github Action](https://github.com/features/actions).

## Example

```yaml
on:
schedule:
# Runs "at 05:00, only on Monday" (see https://crontab.guru)
- cron: '0 5 * * 1'
- cron: "0 5 * * 1"

jobs:
move-to-next-iteration:
name: Move to next iteration
archive-done-tickets:
name: Archive done tickets
runs-on: ubuntu-latest

steps:
- uses: blombard/move-to-next-iteration@master
with:
owner: OrgName
number: 1
token: ${{ secrets.PROJECT_PAT }}
iteration-field: Iteration
iteration: last
new-iteration: current
statuses: 'Todo,In Progress,In Review'
```
Alternatively, you may specify `excluded-statuses`. In this case, all items that _don’t_ have these statuses will be moved to the new iteration. (Note that if `excluded-statuses` is used, `statuses` will be ignored.)

```yaml
on:
schedule:
# Runs "at 05:00, only on Monday" (see https://crontab.guru)
- cron: '0 5 * * 1'
jobs:
move-to-next-iteration:
name: Move to next iteration
runs-on: ubuntu-latest
steps:
- uses: blombard/move-to-next-iteration@master
with:
owner: OrgName
number: 1
token: ${{ secrets.PROJECT_PAT }}
iteration-field: Iteration
iteration: last
new-iteration: current
excluded-statuses: "Done,Won't Fix"
- uses: TimVanMourik/[email protected]
with:
owner: OrgName
number: 1
token: ${{ secrets.PROJECT_PAT }}
iteration-field: Iteration
iteration: last
statuses: "Done"
```
## Inputs
#### owner
The account name of the GitHub organization.
#### number
Project number as you see it in the URL of the project.
#### token
Personal access token or an OAuth token. the `project` scope is required.

#### iteration-field

The name of your iteration field.

#### iteration
Should be `last` or `current`.

#### new-iteration
Should be `current` or `next`.
Should be `last` or `current`.

#### statuses
Statuses of the issues to move to the next iteration.

⚠️ _This setting is ignored if `excluded-statuses` is provided. See below._ ⚠️

#### excluded-statuses
Statuses of the issues that should _not_ be moved.

⚠️ _This setting takes precedence over `statuses`._ ⚠️
Statuses of the issues to archive

## Sources

This action was made possible thanks to https://github.com/gr2m/github-project.
This action was made possible thanks to https://github.com/gr2m/github-project. This was an adaption from https://github.com/blombard/move-to-next-iteration.
146 changes: 71 additions & 75 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8489,8 +8489,6 @@ function projectFieldsNodesToFieldsMap(state, project, nodes) {

// If the field is of type "Iteration", then the `configuration` property will be set.
if (node.configuration) {
acc[userInternalFieldName].configuration = node.configuration;

acc[userInternalFieldName].optionsById = node.configuration.iterations.concat(node.configuration.completedIterations).reduce(
(acc, option) => {
return {
Expand Down Expand Up @@ -8585,52 +8583,52 @@ function projectFieldValueNodeToValue(projectField, node) {
function projectItemNodeToGitHubProjectItem(state, itemNode) {
const fields = itemFieldsNodesToFieldsMap(state, itemNode.fieldValues.nodes);

// const common = {
// type: itemNode.type,
// id: itemNode.id,
// isArchived: itemNode.isArchived,
// fields,
// };

// if (itemNode.type === "DRAFT_ISSUE") {
// return {
// ...common,
// content: {
// id: itemNode.content.id,
// title: itemNode.content.title,
// createdAt: itemNode.content.createdAt,
// assignees: itemNode.content.assignees.nodes.map((node) => node.login),
// },
// };
// }

// if (itemNode.type === "ISSUE" || itemNode.type === "PULL_REQUEST") {
// // item is issue or pull request
// const issue = {
// id: itemNode.content.id,
// number: itemNode.content.number,
// createdAt: itemNode.content.createdAt,
// closed: itemNode.content.closed,
// closedAt: itemNode.content.closedAt,
// assignees: itemNode.content.assignees.nodes.map((node) => node.login),
// labels: itemNode.content.labels.nodes.map((node) => node.name),
// repository: itemNode.content.repository.name,
// milestone: itemNode.content.milestone,
// title: itemNode.content.title,
// url: itemNode.content.url,
// databaseId: itemNode.content.databaseId,
// };

// const content =
// itemNode.type === "ISSUE"
// ? issue
// : { ...issue, merged: itemNode.content.merged };

// return {
// ...common,
// content,
// };
// }
const common = {
type: itemNode.type,
id: itemNode.id,
isArchived: itemNode.isArchived,
fields,
};

if (itemNode.type === "DRAFT_ISSUE") {
return {
...common,
content: {
id: itemNode.content.id,
title: itemNode.content.title,
createdAt: itemNode.content.createdAt,
assignees: itemNode.content.assignees.nodes.map((node) => node.login),
},
};
}

if (itemNode.type === "ISSUE" || itemNode.type === "PULL_REQUEST") {
// item is issue or pull request
const issue = {
id: itemNode.content.id,
number: itemNode.content.number,
createdAt: itemNode.content.createdAt,
closed: itemNode.content.closed,
closedAt: itemNode.content.closedAt,
assignees: itemNode.content.assignees.nodes.map((node) => node.login),
labels: itemNode.content.labels.nodes.map((node) => node.name),
repository: itemNode.content.repository.name,
milestone: itemNode.content.milestone,
title: itemNode.content.title,
url: itemNode.content.url,
databaseId: itemNode.content.databaseId,
};

const content =
itemNode.type === "ISSUE"
? issue
: { ...issue, merged: itemNode.content.merged };

return {
...common,
content,
};
}
/* c8 ignore next 9 */

// fallback: no content properties are set. Currently that's in case of "REDACTED"
Expand Down Expand Up @@ -9671,7 +9669,6 @@ function projectNodeToProperties(state) {
id: state.id,
title: state.title,
url: state.url,
fields: state.fields,
};
}

Expand Down Expand Up @@ -9817,43 +9814,42 @@ class GitHubProject {

const run = async () => {
try {
const owner = core.getInput('owner');
const number = Number(core.getInput('number'));
const token = core.getInput('token');
const iterationField = core.getInput('iteration-field'); // name of the iteration field
const iterationType = core.getInput('iteration'); // last or current
const newiterationType = core.getInput('new-iteration'); // current or next
const statuses = core.getInput('statuses').split(',');
const coreExclusedStatuses = core.getInput('excluded-statuses');
const excludedStatuses = coreExclusedStatuses ? coreExclusedStatuses.split(',') : [];

const project = new GitHubProject({ owner, number, token, fields: { iteration: iterationField } });
const owner = core.getInput("owner");
const number = Number(core.getInput("number"));
const token = core.getInput("token");
const iterationField = core.getInput("iteration-field"); // name of the iteration field
const iterationType = core.getInput("iteration"); // last or current
const statuses = core.getInput("statuses").split(",");

const project = new GitHubProject({
owner,
number,
token,
fields: { iteration: iterationField },
});

const projectData = await project.getProperties();

const lastIteration = projectData.fields.iteration.configuration.completedIterations[0];
const currentIteration = projectData.fields.iteration.configuration.iterations[0];
const nextIteration = projectData.fields.iteration.configuration.iterations[1];
const lastIteration =
projectData.fields.iteration.configuration.completedIterations[0];
const currentIteration =
projectData.fields.iteration.configuration.iterations[0];

const iteration = iterationType === 'last' ? lastIteration : currentIteration;
const newIteration = newiterationType === 'current' ? currentIteration : nextIteration;
const iteration =
iterationType === "last" ? lastIteration : currentIteration;

const items = await project.items.list();

const filteredItems = items.filter(item => {
const filteredItems = items.filter((item) => {
// If item is not in the old iteration, return false.
if (item.fields.iteration !== iteration.title) return false;
// If excludedStatuses are supplied, use that. Otherwise, use statuses.
if (excludedStatuses?.length) {
// Move item only if its status _is not_ in the excluded statuses list.
return !excludedStatuses.includes(item.fields.status);
} else {
// Move item only if its status _is_ in the statuses list.
return statuses.includes(item.fields.status);
}
// Move item only if its status _is_ in the statuses list.
return statuses.includes(item.fields.status);
});

await Promise.all(filteredItems.map(item => project.items.update(item.id, { iteration: newIteration.title })));
await Promise.all(
filteredItems.map((item) => project.items.archive(item.id))
);
} catch (error) {
core.setFailed(error.message);
}
Expand Down
55 changes: 27 additions & 28 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,45 +1,44 @@
import core from '@actions/core';
import GitHubProject from 'github-project';
import core from "@actions/core";
import GitHubProject from "github-project";

const run = async () => {
try {
const owner = core.getInput('owner');
const number = Number(core.getInput('number'));
const token = core.getInput('token');
const iterationField = core.getInput('iteration-field'); // name of the iteration field
const iterationType = core.getInput('iteration'); // last or current
const newiterationType = core.getInput('new-iteration'); // current or next
const statuses = core.getInput('statuses').split(',');
const coreExclusedStatuses = core.getInput('excluded-statuses');
const excludedStatuses = coreExclusedStatuses ? coreExclusedStatuses.split(',') : [];

const project = new GitHubProject({ owner, number, token, fields: { iteration: iterationField } });
const owner = core.getInput("owner");
const number = Number(core.getInput("number"));
const token = core.getInput("token");
const iterationField = core.getInput("iteration-field"); // name of the iteration field
const iterationType = core.getInput("iteration"); // last or current
const statuses = core.getInput("statuses").split(",");

const project = new GitHubProject({
owner,
number,
token,
fields: { iteration: iterationField },
});

const projectData = await project.getProperties();

const lastIteration = projectData.fields.iteration.configuration.completedIterations[0];
const currentIteration = projectData.fields.iteration.configuration.iterations[0];
const nextIteration = projectData.fields.iteration.configuration.iterations[1];
const lastIteration =
projectData.fields.iteration.configuration.completedIterations[0];
const currentIteration =
projectData.fields.iteration.configuration.iterations[0];

const iteration = iterationType === 'last' ? lastIteration : currentIteration;
const newIteration = newiterationType === 'current' ? currentIteration : nextIteration;
const iteration =
iterationType === "last" ? lastIteration : currentIteration;

const items = await project.items.list();

const filteredItems = items.filter(item => {
const filteredItems = items.filter((item) => {
// If item is not in the old iteration, return false.
if (item.fields.iteration !== iteration.title) return false;
// If excludedStatuses are supplied, use that. Otherwise, use statuses.
if (excludedStatuses?.length) {
// Move item only if its status _is not_ in the excluded statuses list.
return !excludedStatuses.includes(item.fields.status);
} else {
// Move item only if its status _is_ in the statuses list.
return statuses.includes(item.fields.status);
}
// Move item only if its status _is_ in the statuses list.
return statuses.includes(item.fields.status);
});

await Promise.all(filteredItems.map(item => project.items.update(item.id, { iteration: newIteration.title })));
await Promise.all(
filteredItems.map((item) => project.items.archive(item.id))
);
} catch (error) {
core.setFailed(error.message);
}
Expand Down

0 comments on commit 96d4d75

Please sign in to comment.