Skip to content

Commit

Permalink
Merge pull request #1 from yuta1024/init
Browse files Browse the repository at this point in the history
initial version
  • Loading branch information
yuta1024 authored Aug 31, 2020
2 parents ed7a149 + a42f23d commit 19957a3
Show file tree
Hide file tree
Showing 22 changed files with 6,254 additions and 1 deletion.
6 changes: 6 additions & 0 deletions .env.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
GITHUB_REPOSITORY=
INPUT_REPO_TOKEN=
INPUT_PROJECT=
INPUT_FROM_COLUMN=
INPUT_TO_COLUMN=
INPUT_EXPIRATION_DAYS=
1 change: 1 addition & 0 deletions .envrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dotenv
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dist/
5 changes: 5 additions & 0 deletions .eslintrc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
env:
es2017: true
jest: true
node: true
extends: eslint:recommended
23 changes: 23 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Test
on:
pull_request:
push:
branches:
- main

jobs:
test:
runs-on: ubuntu-20.04
strategy:
matrix:
node-version: [12.x, 14.x]
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: npm install
- run: npm run lint
- run: npm test
- run: npm run build
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.env
.idea/
coverage/
node_modules/
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Change log

## [1.0.0] - 2020-09-01
### Added
- initial version
59 changes: 58 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,59 @@
# cards-moving-automation
Automate that cards of GitHub Project move to any column with expiration
![Test](https://github.com/yuta1024/cards-moving-automation/workflows/Test/badge.svg)

Automate that cards of GitHub Project move to any column with expiration.

**Now, support only `issue`.**

## Building and testing
Install the dependencies
```bash
$ npm install
```

Run the tests
```bash
$ npm test
```

## Usage
See [action.yml](./action.yml) For comprehensive list of options.

Basic:
```yaml
name: 'Move expired cards'
on:
schedule:
- cron: "0 0 * * *"

jobs:
automation:
runs-on: ubuntu-latest
steps:
- uses: yuta1024/cards-moving-automation@v1
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
project: 'The name of the GitHub Project'
from_column: 'The name of the columns which contains cards to move'
to_column: 'The name of the column to move it into'
```
Configure `expiration_days`:
```yaml
name: 'Move expired cards'
on:
schedule:
- cron: "0 0 * * *"
jobs:
automation:
runs-on: ubuntu-latest
steps:
- uses: yuta1024/cards-moving-automation@v1
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
project: 'The name of the GitHub Project'
from_column: 'The name of the columns which contains cards to move'
to_column: 'The name of the column to move it into'
expiration_days: 30
```
22 changes: 22 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: Cards Moving Automation
description: Automate that cards of GitHub Project move to any column with expiration
inputs:
repo_token:
description: The token for the repository can be passed in using {{ secrets.GITHUB_TOKEN }}
required: true
project:
description: The name of the GitHub Project
required: true
from_column:
description: The name of the columns which contains cards to move
required: true
to_column:
description: The name of the column to move it into
required: true
expiration_days:
description: The minimum days of moving cards with no activity
default: '90'
required: false
runs:
using: node12
main: dist/index.js
1 change: 1 addition & 0 deletions dist/index.js

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const core = require('@actions/core');
const main = require('./lib/main');

(async () => {
try {
await main();
} catch (err) {
core.setFailed(err.message);
}
})();
38 changes: 38 additions & 0 deletions lib/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
const core = require('@actions/core');
const github = require('@actions/github');
const moment = require('moment-timezone');
const project = require('./project');
const validator = require('./validator');

module.exports = async() => {
const now = moment();
const args = await validator.getArgs();
const { owner, repo } = github.context.repo;
const octokit = github.getOctokit(args.token);

const targetProject = (await project.getAllProjects(octokit, owner, repo))
.find(project => project.name === args.project);
if (targetProject === undefined) {
throw new Error(`${args.project} is not found in ${owner}/${repo}`);
}

const targetColumn = (await project.getAllColumns(octokit, owner, repo, targetProject.number))
.find(column => column.name === args.toColumn);
if (targetColumn === undefined) {
throw new Error(`${args.toColumn} is not found in ${args.project}`);
}

const targetIssues = (await project.getAllOpenedIssues(octokit, owner, repo))
.filter(issue =>
issue.projectCards.nodes.length > 0 &&
issue.projectCards.nodes[0].column.name === args.fromColumn &&
issue.projectCards.nodes[0].project.name === args.project &&
moment(issue.updatedAt).add(args.expirationDays, 'd').isBefore(now) &&
moment(issue.projectCards.nodes[0].updatedAt).add(args.expirationDays, 'd').isBefore(now)
);

for (const issue of targetIssues) {
core.info(`move "${issue.title}" from "${args.fromColumn}" to "${args.toColumn}".`)
await project.moveCard(octokit, issue.projectCards.nodes[0].id, targetColumn.id);
}
};
115 changes: 115 additions & 0 deletions lib/project.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
const getAllProjects = async (octokit, owner, repo, cursor = null) => {
const res = await octokit.graphql(`
query($owner: String!, $repo: String!, $cursor: String) {
repository(owner: $owner, name: $repo) {
projects(after: $cursor, first:100) {
pageInfo {
endCursor
hasNextPage
}
nodes {
name
number
}
}
}
}`, {owner, repo, cursor});

const pageInfo = res.repository.projects.pageInfo;
if (pageInfo.hasNextPage) {
return [
...res.repository.projects.nodes,
...await getAllProjects(octokit, owner, repo, pageInfo.endCursor)
];
} else {
return res.repository.projects.nodes;
}
};

const getAllColumns = async (octokit, owner, repo, projectNumber, cursor = null) => {
const res = await octokit.graphql(`
query($owner: String!, $repo: String!, $projectNumber: Int!, $cursor: String) {
repository(owner: $owner, name: $repo) {
project(number: $projectNumber) {
columns(after: $cursor, first:100) {
pageInfo {
endCursor
hasNextPage
}
nodes {
id
name
}
}
}
}
}`, {owner, repo, projectNumber, cursor});

const pageInfo = res.repository.project.columns.pageInfo;
if (pageInfo.hasNextPage) {
return [
...res.repository.project.columns.nodes,
...await getAllColumns(octokit, owner, repo, projectNumber, pageInfo.endCursor)
];
} else {
return res.repository.project.columns.nodes;
}
}

const getAllOpenedIssues = async (octokit, owner, repo, cursor = null) => {
const res = await octokit.graphql(`
query($owner: String!, $repo: String!, $cursor: String) {
repository(owner: $owner, name: $repo) {
issues(after: $cursor, first: 100, filterBy: {states: [OPEN]}) {
pageInfo {
endCursor,
hasNextPage
}
nodes {
title
updatedAt
projectCards(first: 1) {
nodes {
id
column {
name
}
project {
name
}
updatedAt
}
}
}
}
}
}`, {owner, repo, cursor});

const pageInfo = res.repository.issues.pageInfo;
if (pageInfo.hasNextPage) {
return [
...res.repository.issues.nodes,
...await getAllOpenedIssues(octokit, owner, repo, pageInfo.endCursor)
];
} else {
return res.repository.issues.nodes;
}
}

// https://docs.github.com/en/graphql/reference/mutations#moveprojectcard
const moveCard = async (octokit, cardId, columnId) => {
return await octokit.graphql(`
mutation($cardId: ID!, $columnId: ID!) {
moveProjectCard(input: {cardId: $cardId, columnId: $columnId}) {
clientMutationId
}
}
`, {cardId, columnId});
}

module.exports = {
getAllProjects,
getAllColumns,
getAllOpenedIssues,
moveCard
};
17 changes: 17 additions & 0 deletions lib/validator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const core = require('@actions/core');

module.exports.getArgs = async () => {
const args = {
token: core.getInput('repo_token', {required: true}),
project: core.getInput('project', {required: true}),
fromColumn: core.getInput('from_column', {required: true}),
toColumn: core.getInput('to_column', {required: true}),
expirationDays: core.getInput('expiration_days')
}

if (isNaN(parseInt(core.getInput('expiration_days')))) {
throw Error('`expiration-days` must be integer')
}

return args;
};
Loading

0 comments on commit 19957a3

Please sign in to comment.