diff --git a/plugins/github-enricher/gatsby-node.js b/plugins/github-enricher/gatsby-node.js index 01c2bb7576c8..fd302a8299ff 100644 --- a/plugins/github-enricher/gatsby-node.js +++ b/plugins/github-enricher/gatsby-node.js @@ -24,9 +24,9 @@ let getLabels exports.onPreBootstrap = async ({}) => { repoCache = new PersistableCache({ key: "github-api-for-repos", stdTTL: 3 * DAY_IN_SECONDS }) -// The location of extension metadata files is unlikely to change often, and if it does, the link checker will flag the issue +// The location of extension files is unlikely to change often, and if it does, the link checker will flag the issue extensionYamlCache = new PersistableCache({ - key: "github-api-for-extension-metadata", + key: "github-api-for-extension-paths", stdTTL: 10 * DAY_IN_SECONDS }) @@ -222,9 +222,6 @@ const fetchGitHubInfo = async (scmUrl, groupId, artifactId, labels) => { const scmInfo = { url: scmUrl, project } - scmInfo.sponsors = await findSponsor(coords.owner, project) - scmInfo.contributors = await getContributors(coords.owner, project) - // Always set the issuesUrl and labels since the cached one might be invalid scmInfo.issuesUrl = issuesUrl scmInfo.labels = labels @@ -349,10 +346,14 @@ const fetchGitHubInfo = async (scmUrl, groupId, artifactId, labels) => { scmInfo.ownerImageUrl = data?.repositoryOwner?.avatarUrl - let extensionYamlUrl - + let extensionPathInRepo if (hasMetadataFileLocationCache) { - extensionYamlUrl = extensionYamlCache.get(artifactKey) + const paths = extensionYamlCache.get(artifactKey) + const { extensionYamlUrl, extensionRootUrl } = paths + extensionPathInRepo = paths.extensionPathInRepo + scmInfo.extensionYamlUrl = extensionYamlUrl + scmInfo.extensionPathInRepo = extensionPathInRepo + scmInfo.extensionRootUrl = extensionRootUrl } else { const allMetaInfs = [ ...(metaInfs ? metaInfs.entries : []), @@ -370,15 +371,24 @@ const fetchGitHubInfo = async (scmUrl, groupId, artifactId, labels) => { ) // We should only have one extension yaml - if we have more, don't guess, and if we have less, don't set anything if (extensionYamls.length === 1) { - const extensionYamlPath = extensionYamls[0].path + // If we didn't get a branch ref from the cache or from github we're a bit stuck and will have to try again next time if (defaultBranchRef) { - extensionYamlUrl = `${scmUrl}/blob/${defaultBranchRef.name}/${extensionYamlPath}` - extensionYamlCache.set(artifactKey, extensionYamlUrl) + const extensionYamlPath = extensionYamls[0].path + extensionPathInRepo = extensionYamlPath.replace("runtime/src/main/resources/META-INF/quarkus-extension.yaml", "") + const extensionRootUrl = `${scmUrl}/blob/${defaultBranchRef.name}/${extensionPathInRepo}` + const extensionYamlUrl = `${scmUrl}/blob/${defaultBranchRef.name}/${extensionYamlPath}` + extensionYamlCache.set(artifactKey, { extensionYamlUrl, extensionPathInRepo, extensionRootUrl }) + + scmInfo.extensionYamlUrl = extensionYamlUrl + scmInfo.extensionPathInRepo = extensionPathInRepo + scmInfo.extensionRootUrl = extensionRootUrl } } } - scmInfo.extensionYamlUrl = extensionYamlUrl + + scmInfo.sponsors = await findSponsor(coords.owner, project, extensionPathInRepo) + scmInfo.contributors = await getContributors(coords.owner, project, extensionPathInRepo) scmInfo.owner = coords.owner diff --git a/plugins/github-enricher/gatsby-node.test.js b/plugins/github-enricher/gatsby-node.test.js index 1f3d93030e34..058b00a881e3 100644 --- a/plugins/github-enricher/gatsby-node.test.js +++ b/plugins/github-enricher/gatsby-node.test.js @@ -227,6 +227,24 @@ describe("the github data handler", () => { ) }) + it("fills in a path for the extension", () => { + expect(createNode).toHaveBeenCalledWith( + expect.objectContaining({ + extensionPathInRepo: + "some-folder-name/", + }) + ) + }) + + it("fills in a url for the extension", () => { + expect(createNode).toHaveBeenCalledWith( + expect.objectContaining({ + extensionRootUrl: + "http://fake.github.com/someuser/somerepo/blob/unusual/some-folder-name/", + }) + ) + }) + it("invokes the github graphql api", async () => { expect(queryGraphQl).toHaveBeenCalled() expect(queryGraphQl).toHaveBeenCalledWith( @@ -236,7 +254,7 @@ describe("the github data handler", () => { }) it("caches the issue count", async () => { - expect(queryGraphQl).toHaveBeenLastCalledWith(expect.stringMatching(/issues\(states:OPEN/)) + expect(queryGraphQl).toHaveBeenCalledWith(expect.stringMatching(/issues\(states:OPEN/)) // Reset call counts and histories, since the code may not even do a query jest.clearAllMocks() @@ -253,7 +271,7 @@ describe("the github data handler", () => { ) // But it should not ask for the issues - expect(queryGraphQl).not.toHaveBeenLastCalledWith(expect.stringMatching(/issues\(states:OPEN/)) + expect(queryGraphQl).not.toHaveBeenCalledWith(expect.stringMatching(/issues\(states:OPEN/)) // It should set an issue count, even though it didn't ask for one expect(createNode).toHaveBeenLastCalledWith( @@ -262,7 +280,7 @@ describe("the github data handler", () => { }) it("caches the top-level quarkus-extension.yaml", async () => { - expect(queryGraphQl).toHaveBeenLastCalledWith(expect.stringMatching( + expect(queryGraphQl).toHaveBeenCalledWith(expect.stringMatching( /HEAD:runtime\/src\/main\/resources\/META-INF/ ) ) @@ -282,7 +300,7 @@ describe("the github data handler", () => { ) // But it should not ask for the top-level meta-inf listing - expect(queryGraphQl).not.toHaveBeenLastCalledWith( + expect(queryGraphQl).not.toHaveBeenCalledWith( expect.stringMatching(/HEAD:runtime\/src\/main\/resources\/META-INF/), ) @@ -292,6 +310,8 @@ describe("the github data handler", () => { extensionYamlUrl: url + "/blob/unusual/some-folder-name/runtime/src/main/resources/META-INF/quarkus-extension.yaml", + extensionPathInRepo: + "some-folder-name/", }) ) @@ -305,7 +325,7 @@ describe("the github data handler", () => { it("caches the quarkus-extension.yaml in subfolders", async () => { // Sense check of what happened in beforeEach - expect(queryGraphQl).toHaveBeenLastCalledWith( + expect(queryGraphQl).toHaveBeenCalledWith( expect.stringMatching( /HEAD:something\/runtime\/src\/main\/resources\/META-INF/ ), @@ -326,13 +346,13 @@ describe("the github data handler", () => { // And it should not ask for the subfolder meta-inf listing // It possibly won't ask for anything at all - expect(queryGraphQl).not.toHaveBeenLastCalledWith( + expect(queryGraphQl).not.toHaveBeenCalledWith( expect.stringMatching( /HEAD:something\/runtime\/src\/main\/resources\/META-INF/ ), ) - // It should set an extension descriptor path + // It should set an extension descriptor path and extension path expect(createNode).toHaveBeenLastCalledWith( expect.objectContaining({ extensionYamlUrl: @@ -357,7 +377,7 @@ describe("the github data handler", () => { {} ) - expect(queryGraphQl).toHaveBeenLastCalledWith( + expect(queryGraphQl).toHaveBeenCalledWith( expect.stringMatching(/issues\(states:OPEN/), ) @@ -543,7 +563,7 @@ describe("the github data handler", () => { }) it("does not cache the issue count", async () => { - expect(queryGraphQl).toHaveBeenLastCalledWith( + expect(queryGraphQl).toHaveBeenCalledWith( // This is a bit fragile with the assumptions about whitespace and a bit fiddly with the slashes, but it checks we did a query and got the project name right expect.stringMatching(/issues\(states:OPEN/), ) @@ -562,17 +582,17 @@ describe("the github data handler", () => { {} ) - expect(queryGraphQl).toHaveBeenLastCalledWith( + expect(queryGraphQl).toHaveBeenCalledWith( expect.stringMatching(/issues\(states:OPEN/), ) - expect(createNode).toHaveBeenLastCalledWith( + expect(createNode).toHaveBeenCalledWith( expect.objectContaining({ issues: newIssueCount }) ) }) it("does not cache the labels and issue url", async () => { - expect(queryGraphQl).toHaveBeenLastCalledWith( + expect(queryGraphQl).toHaveBeenCalledWith( // This is a bit fragile with the assumptions about whitespace and a bit fiddly with the slashes, but it checks we did a query and got the project name right expect.stringMatching(/issues\(states:OPEN/), ) @@ -592,17 +612,19 @@ describe("the github data handler", () => { {} ) - expect(createNode).toHaveBeenLastCalledWith( + expect(createNode).toHaveBeenCalledWith( expect.objectContaining({ issuesUrl: secondIssuesUrl }) ) // It should return the labels for this extension - expect(createNode).toHaveBeenLastCalledWith( + expect(createNode).toHaveBeenCalledWith( expect.objectContaining({ labels: secondLabels }) ) }) it("caches the top-level quarkus-extension.yaml", async () => { + // Reset call counts + jest.clearAllMocks() // Now re-trigger the invocation await onCreateNode( { @@ -615,14 +637,14 @@ describe("the github data handler", () => { ) // But it should not ask for the top-level meta-inf listing - expect(queryGraphQl).not.toHaveBeenLastCalledWith( + expect(queryGraphQl).not.toHaveBeenCalledWith( expect.stringMatching( /HEAD:runtime\/src\/main\/resources\/META-INF/ ), ) // It should set an extension descriptor path, even though it didn't ask for one - expect(createNode).toHaveBeenLastCalledWith( + expect(createNode).toHaveBeenCalledWith( expect.objectContaining({ extensionYamlUrl: url + @@ -632,6 +654,9 @@ describe("the github data handler", () => { }) it("caches the quarkus-extension.yaml in subfolders", async () => { + // Reset call counts + jest.clearAllMocks() + // Now re-trigger the invocation await onCreateNode( { @@ -644,14 +669,14 @@ describe("the github data handler", () => { ) // And it should not ask for the subfolder meta-inf listing - expect(queryGraphQl).not.toHaveBeenLastCalledWith( + expect(queryGraphQl).not.toHaveBeenCalledWith( expect.stringMatching( /HEAD:second\/runtime\/src\/main\/resources\/META-INF/ ), ) // It should set an extension descriptor path - expect(createNode).toHaveBeenLastCalledWith( + expect(createNode).toHaveBeenCalledWith( expect.objectContaining({ extensionYamlUrl: url + @@ -661,13 +686,14 @@ describe("the github data handler", () => { }) it("caches the image location", async () => { - expect(queryGraphQl).toHaveBeenLastCalledWith( + expect(queryGraphQl).toHaveBeenCalledWith( expect.stringMatching(/issues\(states:OPEN/), ) response.data.repository.issues.totalCount = 7 - const callCount = queryGraphQl.mock.calls.length + // Reset call counts + jest.clearAllMocks() // Now re-trigger the invocation await onCreateNode( @@ -680,10 +706,10 @@ describe("the github data handler", () => { {} ) - expect(queryGraphQl).toHaveBeenCalledTimes(callCount + 1) + expect(queryGraphQl).toHaveBeenCalledTimes(1) // We shouldn't be asking for image urls or file paths, since those are totally cacheable - expect(queryGraphQl).not.toHaveBeenLastCalledWith( + expect(queryGraphQl).not.toHaveBeenCalledWith( expect.stringMatching(/openGraphImageUrl/), ) diff --git a/plugins/github-enricher/sponsorFinder.js b/plugins/github-enricher/sponsorFinder.js index 1b77d8d5235d..171db5882a5f 100644 --- a/plugins/github-enricher/sponsorFinder.js +++ b/plugins/github-enricher/sponsorFinder.js @@ -83,18 +83,19 @@ const getOrSetFromCache = async (cache, key, functionThatReturnsAPromise) => { } -const getUserContributions = async (org, project) => { +const getUserContributions = async (org, project, inPath) => { if (org && project) { - const key = org + ":" + project + const key = org + ":" + project + inPath return await getOrSetFromCache( repoContributorCache, key, - getUserContributionsNoCache.bind(this, org, project) + getUserContributionsNoCache.bind(this, org, project, inPath) ) } } -const getUserContributionsNoCache = async (org, project) => { +const getUserContributionsNoCache = async (org, project, inPath) => { + const path = inPath || "" // We're only doing one, easy, date manipulation, so don't bother with a library const timePeriodInDays = 180 const someMonthsAgo = new Date(Date.now() - timePeriodInDays * DAY_IN_MILLISECONDS).toISOString() @@ -102,27 +103,27 @@ const getUserContributionsNoCache = async (org, project) => { repository(owner: "${org}", name: "${project}") { defaultBranchRef{ target{ - ... on Commit{ - history(since: "${someMonthsAgo}"){ - edges{ - node{ - ... on Commit{ - author { - user { - login - name - company - url + ... on Commit{ + history(path: "${path}", since: "${someMonthsAgo}"){ + edges{ + node{ + ... on Commit{ + author { + user { + login + name + company + url + } } - } - } - } - } - } + } + } + } + } + } } } } -} }` const body = await queryGraphQl(query) @@ -152,9 +153,9 @@ const getUserContributionsNoCache = async (org, project) => { } -const findSponsor = async (org, project) => { +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) + const collatedHistory = await getUserContributions(org, project, path) 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) @@ -165,8 +166,8 @@ const notBot = (user) => { return user.login && !user.login.includes("[bot]") && user.login !== "actions-user" && user.login !== "quarkiversebot" } -const getContributors = async (org, project) => { - const collatedHistory = await getUserContributions(org, project) +const getContributors = async (org, project, path) => { + const collatedHistory = await getUserContributions(org, project, path) return collatedHistory?.map(user => { const { name, login, contributions, url } = user return { name: name || login, login, contributions, url } diff --git a/src/templates/extension-detail.js b/src/templates/extension-detail.js index dd2302b6b043..7a57ffa677aa 100644 --- a/src/templates/extension-detail.js +++ b/src/templates/extension-detail.js @@ -180,6 +180,8 @@ const ExtensionDetailTemplate = ({ "quarkus-extension.yaml" ) + const extensionRootUrl = metadata?.sourceControl?.extensionRootUrl + // Honour manual overrides of the sponsor const sponsors = metadata?.sponsors || metadata?.sponsor || metadata.sourceControl?.sponsors @@ -248,7 +250,12 @@ const ExtensionDetailTemplate = ({ Recent Contributors -

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

+ + {!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).

)} @@ -482,6 +489,7 @@ export const pageQuery = graphql` } } extensionYamlUrl + extensionRootUrl ownerImage { childImageSharp { gatsbyImageData(width: 220)