Skip to content

Commit

Permalink
Allow custom interval checks and labels
Browse files Browse the repository at this point in the history
  • Loading branch information
OldhamMade committed May 20, 2020
1 parent 919aa0c commit a35623d
Show file tree
Hide file tree
Showing 8 changed files with 106 additions and 33 deletions.
32 changes: 29 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# :calendar: GitHub Issue Due Dates Action
Add due dates to GitHub issues - issues are automatically tagged with `Overdue` and `Due in 1 week` labels.
Add due dates to GitHub issues - issues are automatically tagged with labels
when they pass certain date thresholds, as defined by you.

## How it works:
1. Add the following snippet to the top of issues you'd like to assign due dates to:
Expand All @@ -9,7 +10,7 @@ due: 2019-09-19
---
```
2. Create a `.github/workflows/workflow.yml` file with the following contents:
```
```yaml
name: Main Workflow
on:
schedule:
Expand All @@ -23,6 +24,31 @@ jobs:
uses: alexleventer/[email protected]
with:
GH_TOKEN: "${{ secrets.GH_TOKEN }}"
OVERDUE_LABEL: OVERDUE!
INTERVALS: >-
- days: 30
label: Due in 1 month
- days: 14
label: Due in 2 weeks
- days: 7
label: Due in 1 week
- days: 1
label: DUE TOMORROW
```
3. Generate a [personal access GitHub token](https://github.com/settings/tokens).
4. Add the following environment variable to your repository secrets: `GH_TOKEN={{your personal access token}}`.
4. Add the following environment variable to your repository
secrets: `GH_TOKEN={{your personal access token}}`.

## Defining intervals

Intervals are defined as a sequence (list) with a number of `days` and
a `label` to be set.

You can define intervals and labels to meet your requirements; the
above is simply a guide.

### Note the block syntax

The value of the `INTERVALS` key must be interpreted as a string,
and must be a valid YAML block. Your action will fail if you do not
set the block indicator.
8 changes: 4 additions & 4 deletions __tests__/Octokit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,19 @@ describe('Octokit', () => {
await gh.addLabelToIssue(TEST_REPO_AUTHOR, TEST_REPO_NAME, issues[1].number, ['Test']);
const updatedIssue = await gh.get(TEST_REPO_AUTHOR, TEST_REPO_NAME, issues[1].number);
expect(updatedIssue.labels.map(label => label.name).join(', ')).toContain('Test');
await gh.removeLabelFromIssue(TEST_REPO_AUTHOR, TEST_REPO_NAME, 'Test', issues[1].number);
await gh.removeLabelsFromIssue(TEST_REPO_AUTHOR, TEST_REPO_NAME, issues[1].number, ['Test']);
});

it('should remove label from issue without label', async () => {
it('should remove labels from issue without label', async () => {
const issues = await gh.listAllOpenIssues(TEST_REPO_AUTHOR, TEST_REPO_NAME);
const results = await gh.removeLabelFromIssue(TEST_REPO_AUTHOR, TEST_REPO_NAME, 'Test', issues[0].number);
const results = await gh.removeLabelsFromIssue(TEST_REPO_AUTHOR, TEST_REPO_NAME, issues[0].number, ['Test']);
expect(results).toHaveLength(0);
});

it('should remove label from issue with label', async () => {
const issues = await gh.listAllOpenIssues(TEST_REPO_AUTHOR, TEST_REPO_NAME);
await gh.addLabelToIssue(TEST_REPO_AUTHOR, TEST_REPO_NAME, issues[1].number, ['Test']);
const results = await gh.removeLabelFromIssue(TEST_REPO_AUTHOR, TEST_REPO_NAME, 'Test', issues[1].number);
const results = await gh.removeLabelsFromIssue(TEST_REPO_AUTHOR, TEST_REPO_NAME, issues[1].number, ['Test']);
expect(results).toHaveLength(1);
});

Expand Down
8 changes: 8 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ inputs:
GH_TOKEN:
description: GitHub token used to make API requests
required: true
INTERVALS:
description: >-
A list containing a `label` to set when
the ticket is due in the specified `days`
required: true
OVERDUE_LABEL:
description: The label to set when an issue is overdue
required: false
branding:
icon: 'calendar'
color: 'purple'
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"main": "dist/app.js",
"version": "1.0.12",
"version": "1.0.13",
"license": "MIT",
"scripts": {
"build": "tsc",
Expand All @@ -20,6 +20,7 @@
"@actions/github": "^2.2.0",
"@slack/client": "^5.0.2",
"front-matter": "^3.1.0",
"moment": "^2.25.3"
"moment": "^2.25.3",
"yaml": "^1.10.0"
}
}
41 changes: 34 additions & 7 deletions src/app.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,61 @@
import * as core from '@actions/core';
import {GitHub, context} from '@actions/github';
import Octokit from './integrations/Octokit';
import {datesToDue} from './utils/dateUtils';
import {OVERDUE_TAG_NAME, NEXT_WEEK_TAG_NAME} from './constants';
import {datesToDue, byDays} from './utils/dateUtils';
import YAML from 'yaml'

export const run = async () => {
try {
const githubToken = core.getInput('GH_TOKEN');
const inputIntervals = core.getInput('INTERVALS');
const overdueLabel = core.getInput('OVERDUE_LABEL') || "OVERDUE";

if (!githubToken) {
throw new Error('Missing GH_TOKEN environment variable');
}

if (!inputIntervals) {
throw new Error('Missing INTERVALS environment variable');
}

const ok = new Octokit(githubToken);

const issues = await ok.listAllOpenIssues(context.repo.owner, context.repo.repo);
console.log(`Found ${issues.length} open issue(s)`);

const results = await ok.getIssuesWithDueDate(issues);
console.log(`Found ${results.length} issue(s) with due dates`);

const intervals = YAML.parse(inputIntervals).sort(byDays);
const intervalLabels = intervals.map(interval => interval.label);
console.log(`Found ${intervals.length} defined intervals`);

results.forEach(async issue => {
console.log(`Processing issue #${issue.number} with due date of ${issue.due}`);
const daysUtilDueDate = await datesToDue(issue.due);
if (daysUtilDueDate <= 7 && daysUtilDueDate > 0) {
await ok.addLabelToIssue(context.repo.owner, context.repo.repo, issue.number, [NEXT_WEEK_TAG_NAME]);
} else if (daysUtilDueDate <= 0) {
await ok.removeLabelFromIssue(context.repo.owner, context.repo.repo, NEXT_WEEK_TAG_NAME, issue.number);
await ok.addLabelToIssue(context.repo.owner, context.repo.repo, issue.number, [OVERDUE_TAG_NAME]);

if (daysUtilDueDate <= 0) {
await ok.removeLabelsFromIssue(context.repo.owner, context.repo.repo, issue.number, intervalLabels);
await ok.addLabelToIssue(context.repo.owner, context.repo.repo, issue.number, [overdueLabel]);
console.log(`Marked issue #${issue.number} as overdue`);
} else {
for(interval of intervals) {
if (daysUtilDueDate <= interval.days) {
await ok.removeLabelsFromIssue(context.repo.owner, context.repo.repo, issue.number, intervalLabels);
await ok.addLabelToIssue(context.repo.owner, context.repo.repo, issue.number, [interval.label]);
console.log(`Marked issue #${issue.number} with label: ${interval.label}`);
break; // don't process any more intervals
}
}
}

});

return {
ok: true,
issuesProcessed: results.length,
}

} catch (e) {
core.setFailed(e.message);
throw e;
Expand Down
2 changes: 0 additions & 2 deletions src/constants.ts

This file was deleted.

32 changes: 17 additions & 15 deletions src/integrations/Octokit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,29 +27,31 @@ export default class Octokit {
return data;
}

async addLabelToIssue(owner: string, repo: string, issueNumber: number, labels: string[]) {
async addLabelToIssue(owner: string, repo: string, issue_number: number, labels: string[]) {
const {data} = await this.client.issues.addLabels({
owner,
repo,
issue_number: issueNumber,
issue_number,
labels,
});
return data;
}

async removeLabelFromIssue(owner: string, repo: string, name: string, issue_number: number) {
try {
const {data} = await this.client.issues.removeLabel({
owner,
repo,
name,
issue_number,
});
return data;
} catch (e) {
// Do not throw error
return [];
}
async removeLabelsFromIssue(owner: string, repo: string, issue_number: number, labels: string[]) {
labels.forEach(async label => {
try {
const {data} = await this.client.issues.removeLabel({
owner,
repo,
issue_number,
label,
});
return data;
} catch (e) {
// Do not throw error
return [];
}
});
}

async getIssuesWithDueDate(rawIssues: any[]) {
Expand Down
11 changes: 11 additions & 0 deletions src/utils/dateUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,14 @@ export const datesToDue = (date: string) => {
const today = moment();
return eventDate.diff(today, 'days');
};

export const byDays = (a: object, b: object) => {
switch(true) {
case a.days < b.days:
return -1;
case a.days > b.days:
return 1;
default:
return 0;
}
};

0 comments on commit a35623d

Please sign in to comment.