diff --git a/e2es/seedTestData.js b/e2es/seedTestData.js
old mode 100644
new mode 100755
index c8b39d8..b5eff69
--- a/e2es/seedTestData.js
+++ b/e2es/seedTestData.js
@@ -2,7 +2,7 @@ import { copyFile, mkdir } from "fs/promises";
export const createDestinationDirectory = async () => {
console.info("Creating destination directory...");
- await mkdir("./data");
+ await mkdir("./data", { recursive: true });
};
export const seedTestData = async () => {
diff --git a/e2es/testData/repos.json b/e2es/testData/repos.json
index 40e3595..750b656 100644
--- a/e2es/testData/repos.json
+++ b/e2es/testData/repos.json
@@ -11,9 +11,10 @@
"updatedAt": "2024-07-08T12:39:14Z",
"language": "PHP",
"topics": ["composer", "govpress", "packagist", "php"],
- "openIssues": 0,
+ "openIssues": 10,
"dependencies": [],
- "openPrsCount": 0
+ "openPrCount": 0,
+ "openBotPrCount": 0
},
{
"name": "govuk-blogs",
@@ -27,7 +28,8 @@
"topics": ["govpress", "wordpress-theme"],
"openIssues": 1,
"dependencies": [],
- "openPrsCount": 0
+ "openPrCount": 20,
+ "openBotPrCount": 10
},
{
"name": "php-missing",
@@ -41,7 +43,8 @@
"topics": ["composer", "govpress", "packagist", "php"],
"openIssues": 7,
"dependencies": [],
- "openPrsCount": 2
+ "openPrCount": 200,
+ "openBotPrCount": 3
}
]
}
diff --git a/e2es/towtruck.spec.js b/e2es/towtruck.spec.js
index 21dbc13..0f469fb 100644
--- a/e2es/towtruck.spec.js
+++ b/e2es/towtruck.spec.js
@@ -13,10 +13,62 @@ test("has dependency info", async ({ page, baseURL }) => {
"Language",
"Last Updated",
"Open issues count",
- "Open PRs count",
+ "Open PR count",
"Dependencies",
];
tableHeadings.forEach((heading) => {
- page.getByRole("columnheader", { name: heading });
+ expect(page.getByRole("columnheader", { name: heading })).toBeTruthy();
});
+
+ await testSortingForColumn(
+ {
+ name: "Open issues count",
+ topAscending: "govuk-blogs",
+ topDescending: "optionparser",
+ },
+ page
+ );
+
+ await testSortingForColumn(
+ {
+ name: "Open bot PR count",
+ topAscending: "optionparse",
+ topDescending: "govuk-blogs",
+ },
+ page
+ );
+
+ await testSortingForColumn(
+ {
+ name: "Open PR count",
+ topAscending: "optionparser",
+ topDescending: "php-missing",
+ },
+ page
+ );
+
+ await testSortingForColumn(
+ {
+ name: "Updated at",
+ topAscending: "optionparser",
+ topDescending: "govuk-blogs ",
+ },
+ page
+ );
});
+
+const testSortingForColumn = async (
+ { name, topAscending, topDescending },
+ page
+) => {
+ await page.getByRole("link", { name, exact: true }).click();
+ await assertFirstDependencyRow(topAscending, page);
+
+ await page.getByRole("link", { name, exact: true }).click();
+ await assertFirstDependencyRow(topDescending, page);
+};
+
+const assertFirstDependencyRow = async (expectedFirstDependency, page) => {
+ const firstDependencyRow = page.getByRole("row").nth(1);
+ await expect(firstDependencyRow).toContainText(expectedFirstDependency);
+};
diff --git a/index.njk b/index.njk
index d20f674..d17c1c6 100644
--- a/index.njk
+++ b/index.njk
@@ -38,7 +38,8 @@
Language |
Topics |
{{macros.sortableTableHeader("Open issues count", "openIssues", sortDirection, sortBy)}}
- {{macros.sortableTableHeader("Open PRs count", "openPrsCount", sortDirection, sortBy)}}
+ {{macros.sortableTableHeader("Open bot PR count", "openBotPrCount", sortDirection, sortBy)}}
+ {{macros.sortableTableHeader("Open PR count", "openPrCount", sortDirection, sortBy)}}
{{macros.sortableTableHeader("Updated at", "updatedAt", sortDirection, sortBy)}}
Dependencies |
@@ -46,12 +47,13 @@
{% for repo in repos %}
- {{ repo.name }} |
+ {{ repo.name }} |
{{ repo.description }} |
{{ repo.language }} |
{{ repo.topics | join(", ")}} |
{{ repo.openIssues }} |
- {{ repo.openPrsCount }} |
+ {{ repo.openBotPrCount }} |
+ {{ repo.openPrCount }} |
{{ repo.updatedAt }} |
{% if repo.dependencies.length %}
diff --git a/utils/githubApi/fetchAllRepos.js b/utils/githubApi/fetchAllRepos.js
index a9b90b5..c11567c 100644
--- a/utils/githubApi/fetchAllRepos.js
+++ b/utils/githubApi/fetchAllRepos.js
@@ -16,6 +16,8 @@ const fetchAllRepos = async () => {
await OctokitApp.app.eachRepository(async ({ repository, octokit }) => {
if (repository.archived) return;
+ let repo = mapRepoFromApiForStorage(repository);
+
await Promise.all([
await getDependenciesForRepo({
repository,
@@ -25,12 +27,12 @@ const fetchAllRepos = async () => {
repository,
octokit,
}),
- ]).then(([dependencies, openPrsCount]) => {
- repository.dependencies = dependencies;
- repository.openPrsCount = openPrsCount;
+ ]).then(([dependencies, prInfo]) => {
+ repo.dependencies = dependencies;
+ repo = { ...repo, ...prInfo };
});
- repos.push(mapRepoFromApiForStorage(repository));
+ repos.push(repo);
});
return repos;
diff --git a/utils/githubApi/fetchOpenPrs.js b/utils/githubApi/fetchOpenPrs.js
index 2213b0b..cc77fe7 100644
--- a/utils/githubApi/fetchOpenPrs.js
+++ b/utils/githubApi/fetchOpenPrs.js
@@ -8,9 +8,20 @@ export const getOpenPRsForRepo = async ({ octokit, repository }) => {
return octokit.request(repository.pulls_url).then(handlePrsApiResponse);
};
+const depdencyUpdateBots = ["renovate[bot]", "dependabot[bot]"];
+
/**
* Returns the length of the PRs array or 0 if there are no PRs
* @param {any} {data}
* @returns {number}
*/
-export const handlePrsApiResponse = ({ data }) => data?.length || 0;
+export const handlePrsApiResponse = ({ data }) => {
+ const openBotPrCount = data.reduce((acc, pr) => {
+ if (depdencyUpdateBots.includes(pr?.user?.login)) {
+ return acc + 1;
+ }
+ return acc;
+ }, 0);
+
+ return { openPrCount: data?.length || 0, openBotPrCount };
+};
diff --git a/utils/githubApi/fetchOpenPrs.test.js b/utils/githubApi/fetchOpenPrs.test.js
index 3902801..aa58afb 100644
--- a/utils/githubApi/fetchOpenPrs.test.js
+++ b/utils/githubApi/fetchOpenPrs.test.js
@@ -3,11 +3,65 @@ import expect from "node:assert";
import { handlePrsApiResponse } from "./fetchOpenPrs.js";
describe("handlePrsApiResponse", () => {
- it("returns the length of the array containing PRs", () => {
- expect.equal(handlePrsApiResponse({ data: [1, 2, 3] }), 3);
+ describe("openPrCount", () => {
+ it("returns the length of the array containing PRs", () => {
+ const actual = handlePrsApiResponse({ data: [1, 2, 3] });
+ const expected = { openPrCount: 3, openBotPrCount: 0 };
+
+ expect.deepEqual(actual, expected);
+ });
+
+ it("returns 0 if there are no open PRs", () => {
+ const actual = handlePrsApiResponse({ data: [] });
+ const expected = { openPrCount: 0, openBotPrCount: 0 };
+
+ expect.deepEqual(actual, expected);
+ });
});
- it("returns 0 if there are no open PRs", () => {
- expect.equal(handlePrsApiResponse({ data: undefined }), 0);
+ describe("openBotPrCount", () => {
+ it("returns 1 if there is a PR authored by dependabot", () => {
+ const dependabotPr = { user: { login: "dependabot[bot]" } };
+ const actual = handlePrsApiResponse({ data: [dependabotPr] });
+
+ expect.equal(actual.openBotPrCount, 1);
+ });
+
+ it("returns 1 if there is a PR authored by renovate", () => {
+ const renovatePr = { user: { login: "renovate[bot]" } };
+ const actual = handlePrsApiResponse({ data: [renovatePr] });
+
+ expect.equal(actual.openBotPrCount, 1);
+ });
+
+ it("returns 0 if there is a PR authored by other authors", () => {
+ const humanPr = { user: { login: "rich" } };
+ const actual = handlePrsApiResponse({ data: [humanPr] });
+
+ expect.equal(actual.openBotPrCount, 0);
+ });
+
+ it("handles a variety of PR authors correctly", () => {
+ const dependabotPr = {
+ user: {
+ login: "dependabot[bot]",
+ },
+ };
+ const renovatePr = {
+ user: {
+ login: "renovate[bot]",
+ },
+ };
+ const humanPr = {
+ user: {
+ login: "rich",
+ },
+ };
+ const actual = handlePrsApiResponse({
+ data: [dependabotPr, renovatePr, humanPr],
+ });
+
+ expect.equal(actual.openBotPrCount, 2);
+ });
});
});
diff --git a/utils/index.js b/utils/index.js
index c658c86..9be6d4f 100644
--- a/utils/index.js
+++ b/utils/index.js
@@ -171,6 +171,4 @@ export const mapRepoFromApiForStorage = (repo) => ({
language: repo.language,
topics: repo.topics,
openIssues: repo.open_issues,
- dependencies: repo.dependencies,
- openPrsCount: repo.openPrsCount,
});
diff --git a/utils/index.test.js b/utils/index.test.js
index e085198..dbbc8dd 100644
--- a/utils/index.test.js
+++ b/utils/index.test.js
@@ -95,23 +95,6 @@ describe("mapRepoFromStorageToUi", () => {
describe("mapRepo", () => {
it("maps the repo from the data returned from the api", async () => {
- const repoDependencies = [
- {
- user: {
- login: "some-user",
- },
- pull_request: {},
- body: "Here's a pull request to manually update dependency `foo-utils 1.2.3` to `foo-utils 1.2.4`.",
- },
- {
- user: {
- login: "renovate[bot]",
- },
- pull_request: {},
- body: "Configure Renovate",
- },
- ];
-
const apiRepo = {
id: 248204237,
node_id: "MDEwOlJlcG9zaXRvcnkyNDgyMDQyMzc=",
@@ -258,8 +241,6 @@ describe("mapRepoFromStorageToUi", () => {
triage: false,
pull: false,
},
- dependencies: repoDependencies,
- openPrsCount: 0,
};
const repoToSave = {
@@ -276,8 +257,6 @@ describe("mapRepoFromStorageToUi", () => {
language: "Ruby",
topics: ["delivery-plus", "internal", "tech-ops"],
openIssues: 2,
- dependencies: repoDependencies,
- openPrsCount: 0,
};
expect.deepEqual(mapRepoFromApiForStorage(apiRepo), repoToSave);
diff --git a/utils/sorting.js b/utils/sorting.js
index 366fa46..3f2f175 100644
--- a/utils/sorting.js
+++ b/utils/sorting.js
@@ -41,15 +41,18 @@ export const sortByUpdatedAt = (repos, sortDirection) => {
/**
* Sorts repositories based on the specified type and direction.
- * @param {"openPrsCount"|"openIssues"} sortBy - The type of sorting to apply, either by open PRs count or open issues.
+ * @param {"openPrCount"|"openBotPrCount"|"openIssues"|} sortBy - The type of sorting to apply, either by open PRs count or open issues.
* @param {SortDirection} sortDirection - The direction to sort, either "asc" for ascending or "desc" for descending.
* @param {UiRepo[]} repos - An array of repository objects to sort.
* @returns {UiRepo[]} The sorted array of repository objects.
*/
export const sortByType = (repos, sortDirection, sortBy) => {
switch (sortBy) {
- case "openPrsCount":
- return sortByNumericValue(repos, sortDirection, "openPrsCount");
+ case "openPrCount":
+ return sortByNumericValue(repos, sortDirection, "openPrCount");
+
+ case "openBotPrCount":
+ return sortByNumericValue(repos, sortDirection, "openBotPrCount");
case "openIssues":
return sortByNumericValue(repos, sortDirection, "openIssues");
diff --git a/utils/sorting.test.js b/utils/sorting.test.js
index cf2fca4..6a0cdc9 100644
--- a/utils/sorting.test.js
+++ b/utils/sorting.test.js
@@ -48,7 +48,7 @@ describe("sortByNumericValue", () => {
});
});
-describe.only("sortByUpdatedAt", () => {
+describe("sortByUpdatedAt", () => {
it("sorts the repos by the date they were last updated in ascending order", () => {
const reposToSort = [
{ name: "Repo 1", updatedAtISO8601: "2022-01-01T00:00:00Z" },
@@ -81,14 +81,14 @@ describe.only("sortByUpdatedAt", () => {
describe("sortByType", () => {
it("sorts the repos by number of open PRs", () => {
const reposToSort = [
- { name: "Repo 1", openPrsCount: 5 },
- { name: "Repo 2", openPrsCount: 3 },
- { name: "Repo 3", openPrsCount: 7 },
+ { name: "Repo 1", openPrCount: 5 },
+ { name: "Repo 2", openPrCount: 3 },
+ { name: "Repo 3", openPrCount: 7 },
];
expect.deepEqual(
- sortByType(reposToSort, "asc", "openPrsCount"),
- sortByNumericValue(reposToSort, "asc", "openPrsCount")
+ sortByType(reposToSort, "asc", "openPrCount"),
+ sortByNumericValue(reposToSort, "asc", "openPrCount")
);
});