diff --git a/plugins/github-enricher/gatsby-node.js b/plugins/github-enricher/gatsby-node.js index 418c680e395a..d994e19c0965 100644 --- a/plugins/github-enricher/gatsby-node.js +++ b/plugins/github-enricher/gatsby-node.js @@ -230,7 +230,9 @@ const fetchGitHubInfo = async (scmUrl, groupId, artifactId, labels) => { // scmInfo.extensionPathInRepo may be undefined, but these methods will cope with that scmInfo.sponsors = await findSponsor(coords.owner, project, scmInfo.extensionPathInRepo) - scmInfo.contributors = await getContributors(coords.owner, project, scmInfo.extensionPathInRepo) + const { contributors, lastUpdated } = await getContributors(coords.owner, project, scmInfo.extensionPathInRepo) ?? {} + scmInfo.contributors = contributors + scmInfo.lastUpdated = lastUpdated scmInfo.owner = coords.owner @@ -447,6 +449,7 @@ exports.createSchemaCustomization = ({ actions }) => { companies: [String] extensionYamlUrl: String issues: String + lastUpdated: String contributors: [ContributorInfo] sponsors: [String] socialImage: File @link(by: "url") diff --git a/plugins/github-enricher/gatsby-node.test.js b/plugins/github-enricher/gatsby-node.test.js index b35c7f8c2151..442b27576d3a 100644 --- a/plugins/github-enricher/gatsby-node.test.js +++ b/plugins/github-enricher/gatsby-node.test.js @@ -5,9 +5,11 @@ const { onCreateNode, onPreBootstrap, onPluginInit } = require("./gatsby-node") const { createRemoteFileNode } = require("gatsby-source-filesystem") const { queryGraphQl, getRawFileContents, queryRest } = require("./github-helper") +const { getContributors } = require("./sponsorFinder") jest.mock("gatsby-source-filesystem") jest.mock("./github-helper") +jest.mock("./sponsorFinder") const contentDigest = "some content digest" const createNode = jest.fn() @@ -122,6 +124,7 @@ describe("the github data handler", () => { beforeAll(async () => { queryGraphQl.mockResolvedValue(response) + getContributors.mockResolvedValue({ contributors: [{ name: "someone" }], lastUpdated: Date.now() }) await onPreBootstrap({ cache, actions: {} }) }) @@ -209,6 +212,18 @@ describe("the github data handler", () => { ) }) + it("fills in last updated information", async () => { + expect(createNode).toHaveBeenCalledWith( + expect.objectContaining({ lastUpdated: expect.anything() }) + ) + }) + + it("fills in contributor information", async () => { + expect(createNode).toHaveBeenCalledWith( + expect.objectContaining({ contributors: expect.arrayContaining([expect.anything()]) }) + ) + }) + it("does not populate a label", async () => { expect(createNode).not.toHaveBeenCalledWith( expect.objectContaining({ labels: expect.anything() }) diff --git a/plugins/github-enricher/sponsorFinder.js b/plugins/github-enricher/sponsorFinder.js index 145fafab0c44..9c5ab3b45877 100644 --- a/plugins/github-enricher/sponsorFinder.js +++ b/plugins/github-enricher/sponsorFinder.js @@ -103,17 +103,17 @@ const getUserContributionsNoCache = async (org, project, inPath) => { name company url + } } - } - } - } - } - } - } - } - } - } -}` + } + } + } + } + } + } + } + } + }` const body = await queryGraphQl(query) @@ -136,7 +136,7 @@ const getUserContributionsNoCache = async (org, project, inPath) => { return acc }, [])) - return collatedHistory + return { collatedHistory, lastUpdated: Date.now() } } } @@ -144,9 +144,9 @@ const getUserContributionsNoCache = async (org, project, inPath) => { const findSponsor = async (org, project, path) => { // Cache the github response and aggregation, but not the calculation of sponsors, since we may change the algorithm for it - const collatedHistory = await getUserContributions(org, project, path) + const { collatedHistory } = await getUserContributions(org, project, path) ?? {} + // We don't want to persist the sponsor calculations across builds; we could cache it locally but it's probably not worth it if (collatedHistory) { - // We don't want to persist the sponsor calculations across builds; we could cache it locally but it's probably not worth it return findSponsorFromContributorList(collatedHistory) } } @@ -156,11 +156,12 @@ const notBot = (user) => { } const getContributors = async (org, project, path) => { - const collatedHistory = await getUserContributions(org, project, path) - return collatedHistory?.map(user => { + const { collatedHistory, lastUpdated } = await getUserContributions(org, project, path) ?? {} + const contributors = collatedHistory?.map(user => { const { name, login, contributions, url } = user return { name: name || login, login, contributions, url } }).filter(notBot) + return { contributors, lastUpdated } } const findSponsorFromContributorList = async (userContributions) => { @@ -297,7 +298,7 @@ const saveSponsorCache = async () => { await companyCache.persist() console.log("Persisted", companyCache.size(), "cached companies.") await repoContributorCache.persist() - console.log("Persisted contributor information for", repoContributorCache.size(), "cached repositories.") + console.log("Persisted contributor information for", repoContributorCache.size(), "cached repositories and paths within those repositories.") } module.exports = { diff --git a/plugins/github-enricher/sponsorFinder.test.js b/plugins/github-enricher/sponsorFinder.test.js index ea01f1c2d5dc..37b7c8396218 100644 --- a/plugins/github-enricher/sponsorFinder.test.js +++ b/plugins/github-enricher/sponsorFinder.test.js @@ -477,8 +477,8 @@ describe("the github sponsor finder", () => { setMinimumContributionCount(1) const contributors = await getContributors("someorg", "someproject") expect(queryGraphQl).toHaveBeenCalled() - expect(contributors).toHaveLength(3) - expect(contributors[0]).toStrictEqual({ + expect(contributors.contributors).toHaveLength(3) + expect(contributors.contributors[0]).toStrictEqual({ "name": "Doctor Fluffy", login: "someonebouncy", contributions: 5, @@ -486,4 +486,14 @@ describe("the github sponsor finder", () => { }) }) }) + + it("returns last updated information", async () => { + const contributors = await getContributors("someorg", "someproject") + + expect(contributors).toHaveProperty("lastUpdated") + + const now = Date.now() + expect(contributors.lastUpdated / now).toBeCloseTo(1) + + }) }) \ No newline at end of file diff --git a/src/templates/extension-detail.js b/src/templates/extension-detail.js index 7a57ffa677aa..39cb72f0e7e6 100644 --- a/src/templates/extension-detail.js +++ b/src/templates/extension-detail.js @@ -248,19 +248,29 @@ const ExtensionDetailTemplate = ({ {metadata?.sourceControl?.contributors && metadata?.sourceControl?.contributors.length > 0 && ( - - Recent Contributors - - {!extensionRootUrl && ( -

Commits to this extension's repository in the past six months (including merge commits).

)} - {extensionRootUrl && ( -

Commits to this extension's source code in the past six months - (including merge commits).

)} - - - - -
+ Recent Contributors + + {!extensionRootUrl && ( +

Commits to this extension's repository in the past six months (including merge commits).

)} + {extensionRootUrl && ( +

Commits to this extension's source code in the past six months + (including merge commits).

)} + + + + + + {metadata?.sourceControl?.lastUpdated && ( +

Commit statistics last + updated {new Date(+metadata?.sourceControl?.lastUpdated).toLocaleDateString("en-us", { + weekday: "long", + year: "numeric", + month: "short", + day: "numeric", + hour: "numeric", + minute: "numeric" + })}.

+ )}
) } @@ -477,6 +487,7 @@ export const pageQuery = graphql` issues issuesUrl sponsors + lastUpdated contributors { name contributions diff --git a/src/templates/extension-detail.test.js b/src/templates/extension-detail.test.js index f258483982ee..4ccdf2bb966b 100644 --- a/src/templates/extension-detail.test.js +++ b/src/templates/extension-detail.test.js @@ -48,6 +48,7 @@ describe("extension detail page", () => { project: "jproject", issues: 839, sponsors: ["Automatically Calculated Sponsor"], + lastUpdated: "1698924315702", contributors: [{ name: "Alice", contributions: 6 }, { name: "Bob", contributions: 2 }], ownerImage: { childImageSharp: { @@ -197,6 +198,17 @@ describe("extension detail page", () => { expect(screen.getByText("Recent Contributors")).toBeTruthy() }) + it("has last updated information on the community tab", async () => { + const tab = screen.getAllByText("Community")[1] // get the last element, which should be second + await user.click(tab) + const year = new Date().getFullYear() + const lastUpdated = screen.getByText(/last updated/i) + expect(lastUpdated).toBeTruthy() + + expect(lastUpdated.innerHTML).toMatch(" " + year) + + }) + // With the resizable container, we can't see inside the chart at all, sadly xit("renders a committers chart", async () => { // The committers chart is an svg, not an image, but we can find it by title diff --git a/test-integration/detail-page.test.js b/test-integration/detail-page.test.js index f2f4f186bd57..16ef253086c6 100644 --- a/test-integration/detail-page.test.js +++ b/test-integration/detail-page.test.js @@ -61,4 +61,11 @@ describe("an extension details page", () => { page.waitForXPath(`//*[text()="Issues"]`) ).resolves.toBeTruthy() }) + + it("should show a community tab", async () => { + await expect( + page.waitForXPath(`//*[text()="Community"]`) + ).resolves.toBeTruthy() + }) + })