Skip to content

Commit

Permalink
Merge pull request #1 from actions/main
Browse files Browse the repository at this point in the history
  • Loading branch information
C0ZEN authored Nov 22, 2020
2 parents 707ba4d + 87c2b79 commit c21e2f2
Show file tree
Hide file tree
Showing 10 changed files with 1,118 additions and 163 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
node_modules/
lib/
__tests__/runner/*
.idea
364 changes: 343 additions & 21 deletions dist/index.js

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ module.exports = {
clearMocks: true,
moduleFileExtensions: ['js', 'ts'],
testEnvironment: 'node',
testMatch: ['**/*.test.ts'],
testMatch: ['**/*.test.ts', '**/*.spec.ts'],
testRunner: 'jest-circus/runner',
transform: {
'^.+\\.ts$': 'ts-jest'
},
verbose: true
}
};
485 changes: 377 additions & 108 deletions package-lock.json

Large diffs are not rendered by default.

13 changes: 8 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"format": "prettier --write **/*.ts",
"format-check": "prettier --check **/*.ts",
"lint": "eslint src/**/*.ts",
"lint:fix": "eslint src/**/*.ts --fix",
"pack": "ncc build",
"test": "jest",
"all": "npm run build && npm run format && npm run lint && npm run pack && npm test"
Expand All @@ -27,18 +28,20 @@
"dependencies": {
"@actions/core": "^1.2.6",
"@actions/github": "^4.0.0",
"@octokit/rest": "^18.0.4",
"@octokit/rest": "^18.0.9",
"lodash.deburr": "^4.1.0",
"semver": "^7.3.2"
},
"devDependencies": {
"@types/semver": "^7.3.1",
"@types/jest": "^26.0.10",
"@types/jest": "^26.0.15",
"@types/lodash.deburr": "^4.1.6",
"@types/node": "^14.10.0",
"@typescript-eslint/parser": "^3.10.1",
"@types/semver": "^7.3.4",
"@typescript-eslint/parser": "^4.8.1",
"@vercel/ncc": "^0.24.0",
"eslint": "^7.7.0",
"eslint-plugin-github": "^4.0.1",
"eslint-plugin-jest": "^23.20.0",
"eslint-plugin-jest": "^24.1.3",
"jest": "^24.9.0",
"jest-circus": "^26.4.2",
"js-yaml": "^3.14.0",
Expand Down
39 changes: 12 additions & 27 deletions src/IssueProcessor.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import * as core from '@actions/core';
import {context, getOctokit} from '@actions/github';
import {GetResponseTypeFromEndpointMethod} from '@octokit/types';
import {isLabeled} from './functions/is-labeled';
import {labelsToList} from './functions/labels-to-list';

export interface Issue {
title: string;
Expand Down Expand Up @@ -115,7 +117,7 @@ export class IssueProcessor {
const isPr = !!issue.pull_request;

core.info(
`Found issue: issue #${issue.number} - ${issue.title} last updated ${issue.updated_at} (is pr? ${isPr})`
`Found issue: issue #${issue.number} last updated ${issue.updated_at} (is pr? ${isPr})`
);

// calculate string based messages for this issue
Expand All @@ -131,7 +133,7 @@ export class IssueProcessor {
const closeLabel: string = isPr
? this.options.closePrLabel
: this.options.closeIssueLabel;
const exemptLabels = IssueProcessor.parseCommaSeparatedString(
const exemptLabels: string[] = labelsToList(
isPr ? this.options.exemptPrLabels : this.options.exemptIssueLabels
);
const skipMessage = isPr
Expand All @@ -157,15 +159,15 @@ export class IssueProcessor {

if (
exemptLabels.some((exemptLabel: string) =>
IssueProcessor.isLabeled(issue, exemptLabel)
isLabeled(issue, exemptLabel)
)
) {
core.info(`Skipping ${issueType} because it has an exempt label`);
continue; // don't process exempt issues
}

// does this issue have a stale label?
let isStale = IssueProcessor.isLabeled(issue, staleLabel);
let isStale = isLabeled(issue, staleLabel);

// should this issue be marked stale?
const shouldBeStale = !IssueProcessor.updatedSince(
Expand Down Expand Up @@ -250,7 +252,7 @@ export class IssueProcessor {
await this.closeIssue(issue, closeMessage, closeLabel);
} else {
core.info(
`Stale ${issueType} is not old enough to close yet (hasComments? ${issueHasComments}, hasUpdate? ${issueHasUpdate}`
`Stale ${issueType} is not old enough to close yet (hasComments? ${issueHasComments}, hasUpdate? ${issueHasUpdate})`
);
}
}
Expand All @@ -277,7 +279,7 @@ export class IssueProcessor {
);

core.info(
`Comments not made by ${context.actor} or another bot: ${filteredComments.length}`
`Comments not made by actor or another bot: ${filteredComments.length}`
);

// if there are any user comments returned
Expand Down Expand Up @@ -336,7 +338,7 @@ export class IssueProcessor {
staleLabel: string,
skipMessage: boolean
): Promise<void> {
core.info(`Marking issue #${issue.number} - ${issue.title} as stale`);
core.info(`Marking issue #${issue.number} as stale`);

this.staleIssues.push(issue);

Expand Down Expand Up @@ -382,9 +384,7 @@ export class IssueProcessor {
closeMessage?: string,
closeLabel?: string
): Promise<void> {
core.info(
`Closing issue #${issue.number} - ${issue.title} for being stale`
);
core.info(`Closing issue #${issue.number} for being stale`);

this.closedIssues.push(issue);

Expand Down Expand Up @@ -434,9 +434,7 @@ export class IssueProcessor {

// Remove a label from an issue
private async removeLabel(issue: Issue, label: string): Promise<void> {
core.info(
`Removing label ${label} from issue #${issue.number} - ${issue.title}`
);
core.info(`Removing label from issue #${issue.number}`);

this.removedLabelIssues.push(issue);

Expand Down Expand Up @@ -464,7 +462,7 @@ export class IssueProcessor {
issue: Issue,
label: string
): Promise<string | undefined> {
core.info(`Checking for label ${label} on issue #${issue.number}`);
core.info(`Checking for label on issue #${issue.number}`);

this.operationsLeft -= 1;

Expand All @@ -490,24 +488,11 @@ export class IssueProcessor {
return staleLabeledEvent.created_at;
}

private static isLabeled(issue: Issue, label: string): boolean {
const labelComparer: (l: Label) => boolean = l =>
label.localeCompare(l.name, undefined, {sensitivity: 'accent'}) === 0;
return issue.labels.filter(labelComparer).length > 0;
}

private static updatedSince(timestamp: string, num_days: number): boolean {
const daysInMillis = 1000 * 60 * 60 * 24 * num_days;
const millisSinceLastUpdated =
new Date().getTime() - new Date(timestamp).getTime();

return millisSinceLastUpdated <= daysInMillis;
}

private static parseCommaSeparatedString(s: string): string[] {
// String.prototype.split defaults to [''] when called on an empty string
// In this case, we'd prefer to just return an empty array indicating no labels
if (!s.length) return [];
return s.split(',').map(l => l.trim());
}
}
187 changes: 187 additions & 0 deletions src/functions/is-labeled.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
import {Issue} from '../IssueProcessor';
import {isLabeled} from './is-labeled';

describe('isLabeled()', (): void => {
let issue: Issue;
let label: string;

describe('when the given issue contains no label', (): void => {
beforeEach((): void => {
issue = ({
labels: []
} as unknown) as Issue;
});

describe('when the given label is a simple label', (): void => {
beforeEach((): void => {
label = 'label';
});

it('should return false', (): void => {
expect.assertions(1);

const result = isLabeled(issue, label);

expect(result).toStrictEqual(false);
});
});
});

describe('when the given issue contains a simple label', (): void => {
beforeEach((): void => {
issue = {
labels: [
{
name: 'label'
}
]
} as Issue;
});

describe('when the given label is a simple label', (): void => {
beforeEach((): void => {
label = 'label';
});

it('should return true', (): void => {
expect.assertions(1);

const result = isLabeled(issue, label);

expect(result).toStrictEqual(true);
});
});
});

describe('when the given issue contains a kebab case label', (): void => {
beforeEach((): void => {
issue = {
labels: [
{
name: 'kebab-case-label'
}
]
} as Issue;
});

describe('when the given label is a kebab case label', (): void => {
beforeEach((): void => {
label = 'kebab-case-label';
});

it('should return true', (): void => {
expect.assertions(1);

const result = isLabeled(issue, label);

expect(result).toStrictEqual(true);
});
});
});

describe('when the given issue contains a multiple word label', (): void => {
beforeEach((): void => {
issue = {
labels: [
{
name: 'label like a sentence'
}
]
} as Issue;
});

describe('when the given label is a multiple word label', (): void => {
beforeEach((): void => {
label = 'label like a sentence';
});

it('should return true', (): void => {
expect.assertions(1);

const result = isLabeled(issue, label);

expect(result).toStrictEqual(true);
});
});
});

describe('when the given issue contains a multiple word label with %20 spaces', (): void => {
beforeEach((): void => {
issue = {
labels: [
{
name: 'label%20like%20a%20sentence'
}
]
} as Issue;
});

describe('when the given label is a multiple word label with %20 spaces', (): void => {
beforeEach((): void => {
label = 'label%20like%20a%20sentence';
});

it('should return true', (): void => {
expect.assertions(1);

const result = isLabeled(issue, label);

expect(result).toStrictEqual(true);
});
});
});

describe('when the given issue contains a label wih diacritical marks', (): void => {
beforeEach((): void => {
issue = {
labels: [
{
name: 'déjà vu'
}
]
} as Issue;
});

describe('when the given issue contains a label', (): void => {
beforeEach((): void => {
label = 'deja vu';
});

it('should return true', (): void => {
expect.assertions(1);

const result = isLabeled(issue, label);

expect(result).toStrictEqual(true);
});
});

describe('when the given issue contains an uppercase label', (): void => {
beforeEach((): void => {
label = 'DEJA VU';
});

it('should return true', (): void => {
expect.assertions(1);

const result = isLabeled(issue, label);

expect(result).toStrictEqual(true);
});
});

describe('when the given issue contains a label wih diacritical marks', (): void => {
beforeEach((): void => {
label = 'déjà vu';
});

it('should return true', (): void => {
expect.assertions(1);

const result = isLabeled(issue, label);

expect(result).toStrictEqual(true);
});
});
});
});
24 changes: 24 additions & 0 deletions src/functions/is-labeled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import deburr from 'lodash.deburr';
import {Issue, Label} from '../IssueProcessor';

/**
* @description
* Check if the label is listed as a label of the issue
*
* @param {Readonly<Issue>} issue A GitHub issue containing some labels
* @param {Readonly<string>} label The label to check the presence with
*
* @return {boolean} Return true when the given label is also in the issue labels
*/
export function isLabeled(
issue: Readonly<Issue>,
label: Readonly<string>
): boolean {
return !!issue.labels.find((issueLabel: Readonly<Label>): boolean => {
return cleanLabel(label) === cleanLabel(issueLabel.name);
});
}

function cleanLabel(label: Readonly<string>): string {
return deburr(label.toLowerCase());
}
Loading

0 comments on commit c21e2f2

Please sign in to comment.