Skip to content

Commit

Permalink
GithubInfo hook to reduce redundency between Github Info components (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
HyperTHD committed Feb 2, 2022
1 parent 4019bfe commit 95b8850
Show file tree
Hide file tree
Showing 10 changed files with 296 additions and 249 deletions.
133 changes: 133 additions & 0 deletions src/web/src/components/GithubInfoProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { createContext, ReactNode, useMemo } from 'react';
import githubReservedNames from '../githubReservedName';

export interface GithubInfoContextInterface {
issues: string[];
pullRequests: string[];
repos: string[];
commits: string[];
users: string[];
}

const GithubInfoContext = createContext<GithubInfoContextInterface>({
issues: [],
pullRequests: [],
repos: [],
commits: [],
users: [],
});

type Props = {
children: ReactNode;
htmlString?: string;
};

const extractGitHubUrlsFromPost = (htmlString: string): string[] => {
const parser = new DOMParser();
const postDoc = parser.parseFromString(htmlString, 'text/html');

const allGithubLinks = Array.from(
// all links that have href starts with 'https://github.com'
postDoc.querySelectorAll("a[href^='https://github.com']"),
(element) => (element as HTMLAnchorElement).href
);

// unique links only
return allGithubLinks.reduce(
(acc: string[], element) => (acc.includes(element) ? acc : [...acc, element]),
[]
);
};

const GithubInfoProvider = ({ children, htmlString }: Props) => {
const extractedGitHubUrls: string[] = useMemo(
() => (htmlString ? extractGitHubUrlsFromPost(htmlString) : []),
[htmlString]
);

const parseGitHubUrl = (url: string): URL | null => {
try {
const ghUrl = new URL(url);
if (ghUrl.hostname !== 'github.com') {
return null;
}
return ghUrl;
} catch (err) {
return null;
}
};

const filterGitHubUrls = () => {
const issues: Set<string> = new Set();
const pullRequests: Set<string> = new Set();
const repos: Set<string> = new Set();
const commits: Set<string> = new Set();
const users: Set<string> = new Set();

const ghUrls = (
extractedGitHubUrls.map((url) => parseGitHubUrl(url)).filter((url) => url !== null) as URL[]
).filter((url) => !githubReservedNames.includes(url.pathname.split('/').slice(1, 2)[0]));

ghUrls.forEach((url) => {
const { pathname } = url;

// Match urls that start with /<user> and optionally end with /<repo> or /<repo>/<anything-in-between>/<type>/<id>
// <id> can be number, or a mixed of 40 alphanumeric (commit id)
// Ex: /Seneca-CDOT/telescope/pull/2367 ✅
// Ex: /Seneca-CDOT/telescope ✅
// Ex: /Seneca-CDOT/telescope/pull/2367/commits/d3fagd3fagd3fagd3fagd3fagd3fag4d41265748 ✅
// Ex: /Seneca-CDOT/telescope/issues ✅
const matches =
/^\/(?<user>[^/]+)(\/(?<repo>[^/]+)((\/(.*))?(\/(?<type>[^/]+)?\/(?<id>(\d+|\w{40}))\/?$))?)?/gi.exec(
pathname
);

if (matches?.groups) {
const { type, user, repo } = matches.groups;

// if repo defined add to repos
if (repo) {
const repoUrl = `${user}/${repo}`;
repos.add(repoUrl);
}
users.add(user);
switch (type?.toLowerCase()) {
case 'pull':
pullRequests.add(pathname);
break;

case 'issues':
issues.add(pathname);
break;

case 'commit':
case 'commits':
commits.add(pathname);
break;

default:
break;
}
}
});

return {
repos: Array.from(repos),
issues: Array.from(issues),
pullRequests: Array.from(pullRequests),
commits: Array.from(commits),
users: Array.from(users),
};
};

const { repos, issues, commits, users, pullRequests } = filterGitHubUrls();

return (
<GithubInfoContext.Provider value={{ issues, pullRequests, repos, commits, users }}>
{children}
</GithubInfoContext.Provider>
);
};

export default GithubInfoProvider;
export { GithubInfoContext };
12 changes: 5 additions & 7 deletions src/web/src/components/Posts/Commits.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { VscGitCommit } from 'react-icons/vsc';
import { createStyles, makeStyles, Theme } from '@material-ui/core';
import useGithubInfo from '../../hooks/use-githubInfo';

const useStyles = makeStyles((theme: Theme) =>
createStyles({
Expand Down Expand Up @@ -46,21 +47,18 @@ const getCommitInfo = (commit: string) => {
return `${user}/${repo}`;
};

type Props = {
commitUrls: string[];
};

const Commits = ({ commitUrls }: Props) => {
const Commits = () => {
const classes = useStyles();
const { commits } = useGithubInfo();

return (
<div className={classes.GitHubInfo}>
<h2 className={classes.GitHubLinkTitle}>
<VscGitCommit className={classes.icon} />
{commitUrls.length === 1 ? 'Commit' : 'Commits'}
{commits.length === 1 ? 'Commit' : 'Commits'}
</h2>
<ul className={classes.commits}>
{commitUrls.map((url) => (
{commits.map((url) => (
<li key={url} className={classes.commit}>
<a
href={`https://github.com${url}`}
Expand Down
98 changes: 9 additions & 89 deletions src/web/src/components/Posts/GitHubInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Issues from './Issues';
import PullRequests from './PullRequests';
import Commits from './Commits';
import Users from './Users';
import githubReservedNames from '../../githubReservedName';
import useGithubInfo from '../../hooks/use-githubInfo';

const useStyles = makeStyles((theme: Theme) =>
createStyles({
Expand All @@ -24,99 +24,19 @@ const useStyles = makeStyles((theme: Theme) =>
})
);

type Props = {
ghUrls: string[];
};

const parseGitHubUrl = (url: string): URL | null => {
try {
const ghUrl = new URL(url);
if (ghUrl.hostname !== 'github.com') {
return null;
}
return ghUrl;
} catch (err) {
return null;
}
};

export const filterGitHubUrls = (urls: string[]) => {
const issues: Set<string> = new Set();
const pullRequests: Set<string> = new Set();
const repos: Set<string> = new Set();
const commits: Set<string> = new Set();
const users: Set<string> = new Set();

// parse string to URL, and filter non reserved name
const ghUrls = (
urls.map((url) => parseGitHubUrl(url)).filter((url) => url !== null) as URL[]
).filter((url) => !githubReservedNames.includes(url.pathname.split('/').slice(1, 2)[0]));

ghUrls.forEach((url) => {
const { pathname } = url;

// Match urls that start with /<user> and optionally end with /<repo> or /<repo>/<anything-in-between>/<type>/<id>
// <id> can be number, or a mixed of 40 alphanumeric (commit id)
// Ex: /Seneca-CDOT/telescope/pull/2367 ✅
// Ex: /Seneca-CDOT/telescope ✅
// Ex: /Seneca-CDOT/telescope/pull/2367/commits/d3fagd3fagd3fagd3fagd3fagd3fag4d41265748 ✅
// Ex: /Seneca-CDOT/telescope/issues ✅
// Ex: /Seneca-CDOT ✅
const matches =
/^\/(?<user>[^/]+)(\/(?<repo>[^/]+)((\/(.*))?(\/(?<type>[^/]+)?\/(?<id>(\d+|\w{40}))\/?$))?)?/gi.exec(
pathname
);

if (matches?.groups) {
const { type, user, repo } = matches.groups;

// if repo defined add to repos
if (repo) {
const repoUrl = `${user}/${repo}`;
repos.add(repoUrl);
}
users.add(user);
switch (type?.toLowerCase()) {
case 'pull':
pullRequests.add(pathname);
break;

case 'issues':
issues.add(pathname);
break;

case 'commit':
case 'commits':
commits.add(pathname);
break;

default:
break;
}
}
});

return {
repos: Array.from(repos),
issues: Array.from(issues),
pullRequests: Array.from(pullRequests),
commits: Array.from(commits),
users: Array.from(users),
};
};

const GitHubInfo = ({ ghUrls }: Props) => {
const GitHubInfo = () => {
const classes = useStyles();
const { repos, issues, pullRequests, commits, users } = filterGitHubUrls(ghUrls);

const { repos, issues, commits, pullRequests, users } = useGithubInfo();

return (
<ListSubheader component="div" className={classes.root}>
<div className={classes.GitHubInfoContainer}>
{!!repos.length && <Repos repoUrls={repos} />}
{!!issues.length && <Issues issueUrls={issues} />}
{!!pullRequests.length && <PullRequests prUrls={pullRequests} />}
{!!commits.length && <Commits commitUrls={commits} />}
{!!users.length && <Users usernames={users} />}
{!!repos.length && <Repos />}
{!!issues.length && <Issues />}
{!!pullRequests.length && <PullRequests />}
{!!commits.length && <Commits />}
{!!users.length && <Users />}
</div>
</ListSubheader>
);
Expand Down
20 changes: 8 additions & 12 deletions src/web/src/components/Posts/GitHubInfoMobile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,7 @@ import Issues from './Issues';
import PullRequests from './PullRequests';
import Commits from './Commits';
import Users from './Users';
import { filterGitHubUrls } from './GitHubInfo';

type Props = {
ghUrls: string[];
};
import useGithubInfo from '../../hooks/use-githubInfo';

const useStyles = makeStyles((theme: Theme) =>
createStyles({
Expand All @@ -29,19 +25,19 @@ const useStyles = makeStyles((theme: Theme) =>
})
);

const GitHubInfoMobile = ({ ghUrls }: Props) => {
const GitHubInfoMobile = () => {
const classes = useStyles();
const { repos, issues, pullRequests, commits, users } = filterGitHubUrls(ghUrls);
const { repos, issues, pullRequests, commits, users } = useGithubInfo();

return (
<div>
<ListSubheader className={classes.root}>
<div className={classes.GitHubInfoContainer}>
{!!repos.length && <Repos repoUrls={repos} />}
{!!issues.length && <Issues issueUrls={issues} />}
{!!pullRequests.length && <PullRequests prUrls={pullRequests} />}
{!!commits.length && <Commits commitUrls={commits} />}
{!!users.length && <Users usernames={users} />}
{!!repos.length && <Repos />}
{!!issues.length && <Issues />}
{!!pullRequests.length && <PullRequests />}
{!!commits.length && <Commits />}
{!!users.length && <Users />}
</div>
</ListSubheader>
</div>
Expand Down
12 changes: 5 additions & 7 deletions src/web/src/components/Posts/Issues.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { VscIssues } from 'react-icons/vsc';
import { createStyles, makeStyles, Theme } from '@material-ui/core';
import useGithubInfo from '../../hooks/use-githubInfo';

const useStyles = makeStyles((theme: Theme) =>
createStyles({
Expand Down Expand Up @@ -43,21 +44,18 @@ const getIssueInfo = (issue: string) => {
return `${user}/${repo}`;
};

type Props = {
issueUrls: string[];
};

const Issues = ({ issueUrls }: Props) => {
const Issues = () => {
const classes = useStyles();
const { issues } = useGithubInfo();

return (
<div className={classes.GitHubInfo}>
<h2 className={classes.GitHubLinkTitle}>
<VscIssues className={classes.icon} />
{issueUrls.length === 1 ? 'Issue' : 'Issues'}
{issues.length === 1 ? 'Issue' : 'Issues'}
</h2>
<ul className={classes.issues}>
{issueUrls.map((issue) => (
{issues.map((issue) => (
<li key={issue} className={classes.issue}>
<a
href={`https://github.com${issue}`}
Expand Down
Loading

0 comments on commit 95b8850

Please sign in to comment.