Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented linting #16

Merged
merged 5 commits into from
May 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const {
getConfiguration,
getTypescriptOverride,
} = require("@eng-automation/js-style/src/eslint/configuration");

const tsConfParams = { rootDir: __dirname };

const conf = getConfiguration({ typescript: tsConfParams });

const tsConfOverride = getTypescriptOverride(tsConfParams);

module.exports = {
...conf,
overrides: [
...conf.overrides,
{
...tsConfOverride,
rules: {
...tsConfOverride.rules,
"@typescript-eslint/no-floating-promises": "off",
},
},
],
};
10 changes: 9 additions & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ jobs:
- name: Check that the image builds
run: docker build . --file Dockerfile

test-code:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3.3.0
- run: yarn install --frozen-lockfile
- run: yarn lint
- run: yarn build

test-versions:
runs-on: ubuntu-latest
steps:
Expand Down Expand Up @@ -44,7 +52,7 @@ jobs:

tag:
if: github.event_name == 'push'
needs: [test-image, test-versions]
needs: [test-image, test-versions, test-code]
runs-on: ubuntu-latest
permissions:
contents: write
Expand Down
1 change: 1 addition & 0 deletions .prettierrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require("@eng-automation/js-style/src/prettier/configuration")
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@
"node": ">=18.0.0"
},
"scripts": {
"build": "ncc build"
"build": "ncc build",
"typecheck": "tsc --noEmit",
"lint": "yarn eslint --quiet '{*,**/*}.{js,ts}' && yarn prettier --check '{*,**/*}.json' && yarn typecheck",
"fix:eslint": "eslint --fix",
"fix:prettier": "prettier --write",
"fix": "yarn fix:eslint '{*,**/*}.{js,ts}' && yarn fix:prettier '{*,**/*}.json'"
},
"repository": {
"type": "git",
Expand All @@ -30,6 +35,7 @@
"moment": "^2.29.4"
},
"devDependencies": {
"@eng-automation/js-style": "^2.0.0",
"@types/moment": "^2.13.0",
"@types/node": "^18.15.11",
"@vercel/ncc": "^0.36.1",
Expand Down
14 changes: 5 additions & 9 deletions src/filters.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
import moment from "moment";

export const olderThanDays = (issue: IssueData, daysStale: number): boolean => {
return moment().diff(moment(issue.updated_at), "days") > daysStale;
}
export const olderThanDays = (issue: IssueData, daysStale: number): boolean =>
moment().diff(moment(issue.updated_at), "days") > daysStale;

export const byNoComments = (issue: IssueData): boolean => {
return issue.comments === 0;
}
export const byNoComments = (issue: IssueData): boolean => issue.comments === 0;

export const isNotFromAuthor = ({ user }: IssueData, authors: string[]): boolean => {
return !authors.some(author => author.toLowerCase() === user?.login.toLowerCase());
}
export const isNotFromAuthor = ({ user }: IssueData, authors: string[]): boolean =>
!authors.some((author) => author.toLowerCase() === user?.login.toLowerCase());
62 changes: 28 additions & 34 deletions src/github/issuesParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,37 @@ import { debug } from "@actions/core";
import { GitHub } from "@actions/github/lib/utils";
import moment from "moment";

const listForRepo = (octokit: InstanceType<typeof GitHub>, repo: Repo, per_page: number = 100, page: number = 1) => {
return octokit.rest.issues.listForRepo({ ...repo, per_page, state: "open", page });
}
const listForRepo = (octokit: InstanceType<typeof GitHub>, repo: Repo, per_page: number = 100, page: number = 1) =>
octokit.rest.issues.listForRepo({ ...repo, per_page, state: "open", page });

//** Handles the problem of pagination */
//* * Handles the problem of pagination */
const getAllIssues = async (octokit: InstanceType<typeof GitHub>, repo: Repo): Promise<IssueData[]> => {
const perPage = 100;
let currentPage = 1;
const { data } = await listForRepo(octokit, repo, perPage, currentPage);

// GitHub's REST API v3 considers every pull request an issue so we need to get objects without the PR key
let issues = data.filter(data => !data.pull_request);
let fullPage = issues.length > 99;
while (fullPage) {
currentPage++;
debug(`Iterating on page ${currentPage} with ${issues.length} issues`);
const { data } = await listForRepo(octokit, repo, perPage, currentPage)
issues = issues.concat(data);
fullPage = data.length > 99;
}

debug(`Found a total of ${issues.length} issues`);
return issues;
}
const perPage = 100;
let currentPage = 1;
const { data } = await listForRepo(octokit, repo, perPage, currentPage);

// GitHub's REST API v3 considers every pull request an issue so we need to get objects without the PR key
let issues = data.filter((issue) => !issue.pull_request);
let fullPage = issues.length > 99;
while (fullPage) {
currentPage++;
debug(`Iterating on page ${currentPage} with ${issues.length} issues`);
const page = await listForRepo(octokit, repo, perPage, currentPage);
issues = issues.concat(page.data);
fullPage = data.length > 99;
}

debug(`Found a total of ${issues.length} issues`);
return issues;
};

export const fetchIssues = async (octokit: InstanceType<typeof GitHub>, repo: Repo): Promise<IssueData[]> => {
const issues = await getAllIssues(octokit, repo);
debug(`Found elements ${issues.length}`);

// order them from stalest to most recent
const orderedDates = issues.sort((a, b) => {
return b.updated_at > a.updated_at ? -1 : b.updated_at < a.updated_at ? 1 : 0
});
const issues = await getAllIssues(octokit, repo);
debug(`Found elements ${issues.length}`);

return orderedDates;
}
// order them from stalest to most recent
return issues.sort((a, b) => (b.updated_at > a.updated_at ? -1 : b.updated_at < a.updated_at ? 1 : 0));
};

export const filterByDays = (issues: IssueData[], daysStale: number) => {
return issues.filter(issue => moment().diff(moment(issue.updated_at), "days") > daysStale);
}
export const filterByDays = (issues: IssueData[], daysStale: number): IssueData[] =>
issues.filter((issue) => moment().diff(moment(issue.updated_at), "days") > daysStale);
207 changes: 106 additions & 101 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,117 +2,122 @@ import { debug, getBooleanInput, getInput, info, setOutput, summary } from "@act
import { context, getOctokit } from "@actions/github";
import { Context } from "@actions/github/lib/context";
import moment from "moment";

import { byNoComments, isNotFromAuthor, olderThanDays } from "./filters";
import { fetchIssues } from "./github/issuesParser";

const daysSinceDate = (date: string): number => {
return moment().diff(moment(date), 'days')
}
const daysSinceDate = (date: string): number => moment().diff(moment(date), "days");

const getFiltersFromInput = (): Filters => {
const inputDays = Number.parseInt(getInput("days-stale", { required: false }));
const daysStale = isNaN(inputDays) ? 5 : inputDays;
const inputDays = Number.parseInt(getInput("days-stale", { required: false }));
const daysStale = isNaN(inputDays) ? 5 : inputDays;

const noComments = getInput("noComments") ? getBooleanInput("noComments") : false;

let ignoreAuthors: string[] = [];
const authorsToIgnore = getInput("ignoreAuthors");
if (authorsToIgnore) {
ignoreAuthors = authorsToIgnore.split(",");
}

return { daysStale, noComments, notFromAuthor: ignoreAuthors };
};

const generateMarkdownMessage = (issues: IssueData[], repo: { owner: string; repo: string }) => {
const messages = issues.map(
(issue) => ` - [${issue.title}](${issue.html_url}) - Stale for ${daysSinceDate(issue.updated_at)} days`,
);
return `### Repo ${repo.owner}/${repo.repo} has ${issues.length} stale issues\n${messages.join("\n")}`;
};

const getRepo = (ctx: Context): { owner: string; repo: string } => {
let repo = getInput("repo", { required: false });
if (!repo) {
repo = ctx.repo.repo;
}

let owner = getInput("owner", { required: false });
if (!owner) {
owner = ctx.repo.owner;
}

return { repo, owner };
};

const filterIssues = (issues: IssueData[] | undefined, filters: Filters) => {
if (!issues || issues.length < 1) {
return [];
}

let filteredData = issues;
if (filters.daysStale) {
filteredData = filteredData.filter((is) => olderThanDays(is, filters.daysStale));
}
if (filters.noComments) {
filteredData = filteredData.filter(byNoComments);
}
if (filters.notFromAuthor.length > 0) {
filteredData = filteredData.filter((is) => isNotFromAuthor(is, filters.notFromAuthor));
}

return filteredData;
};

const runAction = async (ctx: Context) => {
const repo = getRepo(ctx);
const token = getInput("GITHUB_TOKEN", { required: true });

const noComments = !!getInput("noComments") ? getBooleanInput("noComments") : false;
const filters = getFiltersFromInput();
debug(JSON.stringify(filters));

let ignoreAuthors: string[] = [];
const authorsToIgnore = getInput("ignoreAuthors");
if (authorsToIgnore) {
ignoreAuthors = authorsToIgnore.split(",");
}
const octokit = getOctokit(token);
const staleIssues = await fetchIssues(octokit, repo);

return {
daysStale, noComments, notFromAuthor: ignoreAuthors
}
}
// we filter the issues and see how many are remaining
const filteredIssues = filterIssues(staleIssues, filters);

const generateMarkdownMessage = (issues: IssueData[], repo: { owner: string, repo: string; }) => {
const messages = issues.map(issue => {
return ` - [${issue.title}](${issue.html_url}) - Stale for ${daysSinceDate(issue.updated_at)} days`;
const amountOfStaleIssues = filteredIssues.length;

info(`Found ${amountOfStaleIssues} stale issues.`);
setOutput("repo", `${repo.owner}/${repo.repo}`);
setOutput("stale", amountOfStaleIssues);

if (amountOfStaleIssues > 0) {
const cleanedData = filteredIssues.map((issue) => {
return {
url: issue.html_url,
title: issue.title,
daysStale: daysSinceDate(issue.updated_at),
number: issue.number,
};
});
const markdownMessage = `### Repo ${repo.owner}/${repo.repo} has ${issues.length} stale issues\n${messages.join("\n")}`;
return markdownMessage;
}

const getRepo = (ctx: Context): { owner: string, repo: string } => {
let repo = getInput("repo", { required: false });
if (!repo) {
repo = ctx.repo.repo;
}

let owner = getInput("owner", { required: false });
if (!owner) {
owner = ctx.repo.owner;
}

return { repo, owner };
}

const filterIssues = (issues: IssueData[], filters: Filters) => {
if (!issues || issues.length < 1) {
return [];
}

let filteredData = issues;
if (filters.daysStale) {
filteredData = filteredData.filter(is => olderThanDays(is, filters.daysStale));
}
if (filters.noComments) {
filteredData = filteredData.filter(byNoComments);
}
if (filters.notFromAuthor && filters.notFromAuthor.length > 0) {
filteredData = filteredData.filter(is => isNotFromAuthor(is, filters.notFromAuthor));
}

return filteredData;
}

const runAction = async (ctx: Context) => {
const repo = getRepo(ctx);
const token = getInput("GITHUB_TOKEN", { required: true });

const filters = getFiltersFromInput();
debug(JSON.stringify(filters));

const octokit = getOctokit(token);
const staleIssues = await fetchIssues(octokit, repo);

// we filter the issues and see how many are remaining
const filteredIssues = filterIssues(staleIssues, filters);

const amountOfStaleIssues = filteredIssues.length;

info(`Found ${amountOfStaleIssues} stale issues.`);
setOutput("repo", `${repo.owner}/${repo.repo}`);
setOutput("stale", amountOfStaleIssues);

if (amountOfStaleIssues > 0) {
let cleanedData = filteredIssues.map(issue => {
return {
url: issue.html_url,
title: issue.title,
daysStale: daysSinceDate(issue.updated_at),
number: issue.number
}
});

const jsonData = JSON.stringify(cleanedData)
setOutput("data", jsonData);
debug(jsonData);
const message = generateMarkdownMessage(filteredIssues, repo);
setOutput("message", message);

await summary.addHeading(`${repo.owner}/${repo.repo}`)
.addHeading(`${amountOfStaleIssues} stale issues`, 3)
.addTable([
[{ data: "Title", header: true }, { data: "Days stale", header: true }, { data: "Link", header: true }],
...cleanedData.map(issue => [issue.title, issue.daysStale.toString(), `${repo.owner}/${repo.repo}#${issue.number}`] as string[])
])
.addLink("See all issues", `https://github.com/${repo.owner}/${repo.repo}/issues`).write();
} else {
setOutput("message", `### Repo ${repo.owner}/${repo.repo} has no stale issues`);
info(`Repo ${repo.owner}/${repo.repo} has no stale issues`);
}
}
const jsonData = JSON.stringify(cleanedData);
setOutput("data", jsonData);
debug(jsonData);
const message = generateMarkdownMessage(filteredIssues, repo);
setOutput("message", message);

await summary
.addHeading(`${repo.owner}/${repo.repo}`)
.addHeading(`${amountOfStaleIssues} stale issues`, 3)
.addTable([
[
{ data: "Title", header: true },
{ data: "Days stale", header: true },
{ data: "Link", header: true },
],
...cleanedData.map(
(issue) =>
[issue.title, issue.daysStale.toString(), `${repo.owner}/${repo.repo}#${issue.number}`] as string[],
),
])
.addLink("See all issues", `https://github.com/${repo.owner}/${repo.repo}/issues`)
.write();
} else {
setOutput("message", `### Repo ${repo.owner}/${repo.repo} has no stale issues`);
info(`Repo ${repo.owner}/${repo.repo} has no stale issues`);
}
};

runAction(context);
Loading