Skip to content

Commit

Permalink
Add GitHub components for Repos, Issues, PullRequests
Browse files Browse the repository at this point in the history
Co-authored-by: Francesco Menghi <[email protected]>
Co-authored-by: Andrew Nguyen <[email protected]>
  • Loading branch information
2 people authored and humphd committed Oct 21, 2021
1 parent 411543e commit bce0b21
Show file tree
Hide file tree
Showing 5 changed files with 326 additions and 1 deletion.
108 changes: 108 additions & 0 deletions src/web/src/components/Posts/GitHubInfo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { createStyles, makeStyles, Theme, ListSubheader } from '@material-ui/core';
import Repos from './Repos';
import Issues from './Issues';
import PullRequests from './PullRequests';

const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
padding: '0',
display: 'flex',
borderLeft: '1.5px solid #999999',
flexDirection: 'column',
[theme.breakpoints.up('lg')]: {
width: '21rem',
},
color: theme.palette.text.secondary,
},
GitHubInfoContainer: {
margin: '2rem 0 0 1rem',
},
})
);

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

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 ghUrls = urls.map((url) => parseGitHubUrl(url)).filter((url) => url !== null) as URL[];

for (const url of ghUrls) {
const { pathname } = url;

// Match urls that start with /<user>/<repo>, and optionally end with /<anything-in-between>/<type>/<id>
// Ex: /Seneca-CDOT/telescope/pull/2367 ✅
// Ex: /Seneca-CDOT/telescope ✅
// Ex: /Seneca-CDOT/telescope/pull/2367/commits/d3fag ✅
// Ex: /Seneca-CDOT/telescope/issues ✅
const matches = /^\/(?<user>[^\/]+)\/(?<repo>[^\/]+)((\/(.*))?\/(?<type>[^\/]+)\/(?<id>(\d+))\/?$)?/i.exec(
pathname
);
if (matches?.groups === undefined) {
continue;
}
const { type, user, repo } = matches.groups;

const repoUrl = `${user}/${repo}`;
repos.add(repoUrl);
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),
};
};

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 GitHubInfo = ({ ghUrls }: Props) => {
const classes = useStyles();
const { repos, issues, pullRequests } = filterGitHubUrls(ghUrls);

return (
<ListSubheader className={classes.root}>
<div className={classes.GitHubInfoContainer}>
{!!repos.length && <Repos repoUrls={repos} />}
{!!issues.length && <Issues issueUrls={issues} />}
{!!pullRequests.length && <PullRequests prUrls={pullRequests} />}
</div>
</ListSubheader>
);
};

export default GitHubInfo;
71 changes: 71 additions & 0 deletions src/web/src/components/Posts/Issues.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { VscIssues } from 'react-icons/vsc';
import { createStyles, makeStyles, Theme } from '@material-ui/core';

const useStyles = makeStyles((theme: Theme) =>
createStyles({
link: {
textDecoration: 'none',
color: theme.palette.text.primary,
'&:hover': {
textDecorationLine: 'underline',
},
},
GitHubInfo: {
lineHeight: '2rem',
fontSize: '1.2rem',
},
GitHubLinkTitle: {
fontSize: '1.4rem',
margin: 0,
paddingTop: '1rem',
},
icon: {
fontSize: '2rem',
marginRight: '1rem',
verticalAlign: 'text-bottom',
},
issues: {
display: 'flex',
margin: 0,
},
issue: {
marginRight: '2rem',
},
})
);

const getIssueNumber = (issue: string) => issue.replace(/.+\/issues\/([0-9]+).*/, '$1');

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

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

return (
<div className={classes.GitHubInfo}>
<h2 className={classes.GitHubLinkTitle}>
<VscIssues className={classes.icon}></VscIssues>
{issueUrls.length === 1 ? 'Issue' : 'Issues'}
</h2>
<p className={classes.issues}>
{issueUrls.map((issue) => (
<p key={issue} className={classes.issue}>
<a
href={`https://github.com${issue}`}
rel="bookmark"
target="_blank"
title={'Issue #' + getIssueNumber(issue)}
className={classes.link}
>
#{getIssueNumber(issue)}
</a>
</p>
))}
</p>
</div>
);
};

export default Issues;
9 changes: 8 additions & 1 deletion src/web/src/components/Posts/Post.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useRef, useState } from 'react';
import { useRef, useState, useMemo } from 'react';
import useSWR from 'swr';
import { makeStyles, Theme, useTheme } from '@material-ui/core/styles';
import {
Expand All @@ -15,6 +15,7 @@ import AdminButtons from '../AdminButtons';
import Spinner from '../Spinner';
import PostDesktopInfo from './PostInfo';
import PostAvatar from './PostAvatar';
import GitHubInfo from './GitHubInfo';

type Props = {
postUrl: string;
Expand Down Expand Up @@ -243,6 +244,11 @@ const PostComponent = ({ postUrl }: Props) => {
// Grab the post data from our backend so we can render it
const { data: post, error } = useSWR<Post>(postUrl);
const [expandHeader, setExpandHeader] = useState(false);
// Extract all the github urls from the post
const extractedGitHubUrls: string[] = useMemo(
() => (post?.html ? extractGitHubUrlsFromPost(post.html) : []),
[post?.html]
);

if (error) {
console.error(`Error loading post at ${postUrl}`, error);
Expand Down Expand Up @@ -331,6 +337,7 @@ const PostComponent = ({ postUrl }: Props) => {
postDate={formatPublishedDate(post.updated)}
blogUrl={post.feed.link}
/>
{!!extractedGitHubUrls.length && <GitHubInfo ghUrls={extractedGitHubUrls} />}
</ListSubheader>
)}
<div className={classes.content}>
Expand Down
72 changes: 72 additions & 0 deletions src/web/src/components/Posts/PullRequests.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { VscGitPullRequest } from 'react-icons/vsc';
import { createStyles, makeStyles, Theme } from '@material-ui/core';

const useStyles = makeStyles((theme: Theme) =>
createStyles({
link: {
textDecoration: 'none',
color: theme.palette.text.primary,
'&:hover': {
textDecorationLine: 'underline',
},
},
GitHubInfo: {
lineHeight: '2rem',
fontSize: '1.2rem',
},
GitHubLinkTitle: {
fontSize: '1.4rem',
margin: 0,
paddingTop: '1rem',
},
icon: {
fontSize: '2rem',
marginRight: '1rem',
verticalAlign: 'text-bottom',
},
pullRequests: {
display: 'flex',
margin: 0,
},
pullRequest: {
marginRight: '2rem',
},
})
);

const getPullRequestNumber = (pullRequest: string) =>
pullRequest.replace(/.+\/pull\/([0-9]+).*/, '$1');

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

const PullRequests = ({ prUrls }: Props) => {
const classes = useStyles();

return (
<div className={classes.GitHubInfo}>
<h2 className={classes.GitHubLinkTitle}>
<VscGitPullRequest className={classes.icon}></VscGitPullRequest>
{prUrls.length === 1 ? 'Pull Request' : 'Pull Requests'}
</h2>
<p className={classes.pullRequests}>
{prUrls.map((pullRequest) => (
<p key={pullRequest} className={classes.pullRequest}>
<a
href={`https://github.com${pullRequest}`}
rel="bookmark"
target="_blank"
title={'Pull Request #' + getPullRequestNumber(pullRequest)}
className={classes.link}
>
#{getPullRequestNumber(pullRequest)}
</a>
</p>
))}
</p>
</div>
);
};

export default PullRequests;
67 changes: 67 additions & 0 deletions src/web/src/components/Posts/Repos.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { VscRepoForked } from 'react-icons/vsc';
import { createStyles, makeStyles, Theme } from '@material-ui/core';

const useStyles = makeStyles((theme: Theme) =>
createStyles({
link: {
textDecoration: 'none',
color: theme.palette.text.primary,
'&:hover': {
textDecorationLine: 'underline',
},
},
GitHubInfo: {
lineHeight: '2rem',
fontSize: '1.2rem',
wordWrap: 'break-word',
},
GitHubLinkTitle: {
fontSize: '1.4rem',
margin: 0,
paddingTop: '1rem',
},
icon: {
fontSize: '2rem',
marginRight: '1rem',
verticalAlign: 'text-bottom',
},
repo: {
marginTop: '2rem',
lineHeight: '0.5rem',
},
})
);

const getRepoName = (repo: string) => repo.replace(/[^\/]+\/([^\/]+).*/, '$1');

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

const Repos = ({ repoUrls }: Props) => {
const classes = useStyles();

return (
<div className={classes.GitHubInfo}>
<h2 className={classes.GitHubLinkTitle}>
<VscRepoForked className={classes.icon}></VscRepoForked>
{repoUrls.length === 1 ? 'Repo' : 'Repos'}
</h2>
{repoUrls.map((repo) => (
<p key={repo} className={classes.repo}>
<a
href={`https://github.com/${repo}`}
rel="bookmark"
target="_blank"
title={repo}
className={classes.link}
>
{getRepoName(repo)}
</a>
</p>
))}
</div>
);
};

export default Repos;

0 comments on commit bce0b21

Please sign in to comment.