diff --git a/components/molecules/ContributorHighlight/contributor-highlight-card.tsx b/components/molecules/ContributorHighlight/contributor-highlight-card.tsx index 0b93274467..b0d06ab13f 100644 --- a/components/molecules/ContributorHighlight/contributor-highlight-card.tsx +++ b/components/molecules/ContributorHighlight/contributor-highlight-card.tsx @@ -25,7 +25,7 @@ import { import useSupabaseAuth from "lib/hooks/useSupabaseAuth"; import { - generateApiPrUrl, + generateRepoParts, getAvatarByUsername, getOwnerAndRepoNameFromUrl, isValidIssueUrl, @@ -219,7 +219,7 @@ const ContributorHighlightCard = ({ } if (isValidPullRequestUrl(pullrequestLink) || isValidIssueUrl(pullrequestLink)) { - const { apiPaths } = generateApiPrUrl(highlight.prLink); + const { apiPaths } = generateRepoParts(highlight.prLink); const { repoName, orgName, issueId } = apiPaths; setLoading(true); const isIssue = highlight.prLink.includes("issues"); diff --git a/components/molecules/HighlightInput/highlight-input-form.tsx b/components/molecules/HighlightInput/highlight-input-form.tsx index 0840bb2277..c6ddae14e4 100644 --- a/components/molecules/HighlightInput/highlight-input-form.tsx +++ b/components/molecules/HighlightInput/highlight-input-form.tsx @@ -11,7 +11,7 @@ import Tooltip from "components/atoms/Tooltip/tooltip"; import { createHighlights } from "lib/hooks/createHighlights"; import { - generateApiPrUrl, + generateRepoParts, getGithubIssueDetails, getGithubIssueComments, getPullRequestCommitMessageFromUrl, @@ -112,9 +112,9 @@ const HighlightInputForm = ({ refreshCallback }: HighlightInputFormProps): JSX.E const highlight = bodyText; if (isValidPullRequestUrl(pullrequestLink) || isValidIssueUrl(pullrequestLink)) { - // generateApiPrUrl will return an object with repoName, orgName and issueId + // generateRepoParts will return an object with repoName, orgName and issueId // it can work with both issue and pull request links - const { apiPaths } = generateApiPrUrl(pullrequestLink); + const { apiPaths } = generateRepoParts(pullrequestLink); const { repoName, orgName, issueId } = apiPaths; setLoading(true); // Api validation to check validity of github pull request link match diff --git a/components/organisms/InsightPage/InsightPage.tsx b/components/organisms/InsightPage/InsightPage.tsx index 0640fadc5e..06ef0be62f 100644 --- a/components/organisms/InsightPage/InsightPage.tsx +++ b/components/organisms/InsightPage/InsightPage.tsx @@ -14,7 +14,7 @@ import RepoNotIndexed from "components/organisms/Repositories/repository-not-ind import useRepositories from "lib/hooks/api/useRepositories"; import useSupabaseAuth from "lib/hooks/useSupabaseAuth"; -import { getAvatarById, getAvatarByUsername } from "lib/utils/github"; +import { generateRepoParts, getAvatarById, getAvatarByUsername } from "lib/utils/github"; import useStore from "lib/store"; import Error from "components/atoms/Error/Error"; import Search from "components/atoms/Search/search"; @@ -202,10 +202,20 @@ const InsightPage = ({ edit, insight, pageRepos }: InsightPageProps) => { return; } + const { apiPaths, isValidUrl } = generateRepoParts(repoToAdd); + + if (!isValidUrl) { + setAddRepoError(RepoLookupError.Invalid); + return; + } + + const { repoFullName } = apiPaths; + setAddRepoLoading({ repoName: repoToAdd, isAddedFromCart, isLoading: true }); try { - const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/repos/${repoToAdd}`); + const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/repos/${repoFullName}`); setAddRepoLoading({ repoName: repoToAdd, isAddedFromCart, isLoading: false }); + if (response.ok) { const addedRepo = (await response.json()) as DbRepo; @@ -215,7 +225,7 @@ const InsightPage = ({ edit, insight, pageRepos }: InsightPageProps) => { setAddRepoError(RepoLookupError.Initial); setRepoSearchTerm(""); } else { - const publicRepoResponse = await fetch(`https://api.github.com/repos/${repoToAdd}`); + const publicRepoResponse = await fetch(`https://api.github.com/repos/${repoFullName}`); if (publicRepoResponse.ok) { const publicRepo = await publicRepoResponse.json(); @@ -390,7 +400,7 @@ const InsightPage = ({ edit, insight, pageRepos }: InsightPageProps) => { `https://github.com/${userna const getRepoIssuesLink = (repoName: string | null) => `https://github.com/${(repoName && `${repoName}/issues`) || ""}`; -const generateApiPrUrl = ( +const generateRepoParts = ( url: string -): { isValidUrl: boolean; apiPaths: { orgName: string | null; repoName: string | null; issueId: string | null } } => { +): { + isValidUrl: boolean; + apiPaths: { orgName: string | null; repoName: string | null; repoFullName: string | null; issueId: string | null }; +} => { try { const trimmedUrl = url.trim(); + + if ( + !(trimmedUrl.includes("https://") || trimmedUrl.includes("http://") || trimmedUrl.includes("www.")) && + trimmedUrl.split("/").length === 2 + ) { + const [orgName, repoName] = trimmedUrl.split("/"); + + const repoFullName = `${orgName}/${repoName}`; + + return { + isValidUrl: true, + apiPaths: { orgName, repoName, repoFullName, issueId: null }, + }; + } + const githubUrl = new URL(trimmedUrl.includes("https://") ? trimmedUrl : `https://${trimmedUrl}`); const { pathname } = githubUrl; const [, orgName, repoName, , issueId] = pathname.split("/"); - if (githubUrl.hostname !== "github.com") { + const repoFullName = `${orgName}/${repoName}`; + + if (githubUrl.hostname !== "github.com" || !orgName || !repoName) { return { isValidUrl: false, - apiPaths: { orgName: null, repoName: null, issueId: null }, + apiPaths: { orgName: null, repoName: null, repoFullName: null, issueId: null }, + }; + } + + if (!issueId) { + return { + isValidUrl: true, + apiPaths: { orgName, repoName, repoFullName, issueId: null }, }; } return { isValidUrl: true, - apiPaths: { orgName, repoName, issueId }, + apiPaths: { orgName, repoName, repoFullName, issueId }, }; } catch (err) { - return { isValidUrl: false, apiPaths: { orgName: null, repoName: null, issueId: null } }; + return { isValidUrl: false, apiPaths: { orgName: null, repoName: null, repoFullName: null, issueId: null } }; } }; @@ -144,7 +171,7 @@ export { getAvatarByUsername, getProfileLink, getRepoIssuesLink, - generateApiPrUrl, + generateRepoParts, generateGhOgImage, isValidPullRequestUrl, isValidIssueUrl, diff --git a/tests/lib/utils/github.test.ts b/tests/lib/utils/github.test.ts index 123ec1d566..4bdb27b7b6 100644 --- a/tests/lib/utils/github.test.ts +++ b/tests/lib/utils/github.test.ts @@ -4,6 +4,7 @@ import { getProfileLink, getRepoIssuesLink, generateGhOgImage, + generateRepoParts, isValidIssueUrl, } from "lib/utils/github"; @@ -35,6 +36,54 @@ describe("[lib] github methods", () => { const result = generateGhOgImage("https://gitub.com/open-sauced/hot/pull/448"); expect(result).toEqual({ isValid: false, url: "" }); }); + it("Should return an object with valid org name, repo name and issue", () => { + const result = generateRepoParts("https://github.com/open-sauced/insights/pull/1470"); + expect(result.isValidUrl).toEqual(true); + expect(result.apiPaths).toEqual({ + orgName: "open-sauced", + repoName: "insights", + repoFullName: "open-sauced/insights", + issueId: "1470", + }); + }); + it("Should return an object with valid org name, repo name and issue", () => { + const result = generateRepoParts("github.com/open-sauced/insights/pull/1470"); + expect(result.isValidUrl).toEqual(true); + expect(result.apiPaths).toEqual({ + orgName: "open-sauced", + repoName: "insights", + repoFullName: "open-sauced/insights", + issueId: "1470", + }); + }); + it("Should return an object with a valid org name and repo name", () => { + const result = generateRepoParts("https://github.com/open-sauced/insights"); + expect(result.isValidUrl).toEqual(true); + expect(result.apiPaths).toEqual({ + orgName: "open-sauced", + repoName: "insights", + repoFullName: "open-sauced/insights", + issueId: null, + }); + }); + it("Should return an object with a valid org name and repo name", () => { + const result = generateRepoParts("open-sauced/insights"); + expect(result.isValidUrl).toEqual(true); + expect(result.apiPaths).toEqual({ + orgName: "open-sauced", + repoName: "insights", + repoFullName: "open-sauced/insights", + issueId: null, + }); + }); + it("Should return an object with isValidUrl set to false", () => { + const result = generateRepoParts("https://insights.opensauced.pizza/hub/insights/new"); + expect(result.isValidUrl).toBeFalsy(); + }); + it("Should return an object with isValidUrl set to false", () => { + const result = generateRepoParts("🍕"); + expect(result.isValidUrl).toBeFalsy(); + }); it("Should return false", () => { const result = isValidIssueUrl("https://gitub.com/open-sauced/hot/pull/448"); expect(result).toEqual(false);