Website improvements 4 (#3182)
* Add contributor individual page * Improve mobile menu * Fix * Remove yarn.lock from twenty-website * Add yarn to gitingore * Fix linter
This commit is contained in:
@ -0,0 +1,9 @@
|
||||
'use client'
|
||||
|
||||
import { ResponsiveTimeRange } from '@nivo/calendar'
|
||||
|
||||
export const ActivityLog = ({ data }: { data: { value: number, day: string }[] }) => {
|
||||
return <ResponsiveTimeRange
|
||||
data={data}
|
||||
/>;
|
||||
}
|
||||
@ -0,0 +1,100 @@
|
||||
import Image from 'next/image';
|
||||
import Database from 'better-sqlite3';
|
||||
import AvatarGrid from '@/app/components/AvatarGrid';
|
||||
import { ResponsiveTimeRange } from '@nivo/calendar'
|
||||
import { ActivityLog } from './components/ActivityLog';
|
||||
import { Metadata } from 'next';
|
||||
|
||||
interface Contributor {
|
||||
login: string;
|
||||
avatarUrl: string;
|
||||
pullRequestCount: number;
|
||||
}
|
||||
|
||||
|
||||
export function generateMetadata({ params }: { params: { slug: string } }): Metadata {
|
||||
return {
|
||||
title: params.slug + ' | Contributors',
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
export default async function (
|
||||
{ params }: { params: { slug: string } }) {
|
||||
|
||||
const db = new Database('db.sqlite', { readonly: true });
|
||||
|
||||
const contributor = db.prepare(`
|
||||
SELECT
|
||||
u.login,
|
||||
u.avatarUrl,
|
||||
(SELECT COUNT(*) FROM pullRequests WHERE authorId = u.id) AS pullRequestCount,
|
||||
(SELECT COUNT(*) FROM issues WHERE authorId = u.id) AS issuesCount
|
||||
FROM
|
||||
users u
|
||||
WHERE
|
||||
u.login = :user_id
|
||||
`).get({'user_id' : params.slug}) as Contributor;
|
||||
|
||||
const pullRequestActivity = db.prepare(`
|
||||
SELECT
|
||||
COUNT(*) as value,
|
||||
DATE(createdAt) as day
|
||||
FROM
|
||||
pullRequests
|
||||
WHERE
|
||||
authorId = (SELECT id FROM users WHERE login = :user_id)
|
||||
GROUP BY
|
||||
DATE(createdAt)
|
||||
ORDER BY
|
||||
DATE(createdAt)
|
||||
`).all({'user_id': params.slug}) as { value: number, day: string }[];
|
||||
|
||||
|
||||
|
||||
const pullRequestList = db.prepare(`
|
||||
SELECT
|
||||
id,
|
||||
title,
|
||||
body,
|
||||
url,
|
||||
createdAt,
|
||||
updatedAt,
|
||||
closedAt,
|
||||
mergedAt
|
||||
FROM
|
||||
pullRequests
|
||||
WHERE
|
||||
authorId = (SELECT id FROM users WHERE login = :user_id)
|
||||
ORDER BY
|
||||
DATE(createdAt) DESC
|
||||
`).all({'user_id': params.slug}) as { title: string, createdAt: string, url: string }[];
|
||||
|
||||
|
||||
db.close();
|
||||
|
||||
|
||||
return (
|
||||
<div style={{maxWidth: '900px', display: 'flex', padding: '40px', gap: '24px'}}>
|
||||
<div style={{ flexDirection: 'column', width: '240px'}}>
|
||||
<Image src={contributor.avatarUrl} alt={contributor.login} width={240} height={240} />
|
||||
<h1>{contributor.login}</h1>
|
||||
</div>
|
||||
<div style={{flexDirection: 'column'}}>
|
||||
<div style={{width: '450px', height: '200px'}}>
|
||||
<ActivityLog
|
||||
data={pullRequestActivity}
|
||||
/>
|
||||
</div>
|
||||
<div style={{width: '450px'}}>
|
||||
{pullRequestList.map(pr => (
|
||||
<div>
|
||||
<a href={pr.url}>{pr.title}</a>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -7,7 +7,7 @@ export async function GET(
|
||||
{ params }: { params: { slug: string } }) {
|
||||
const db = new Database('db.sqlite', { readonly: true });
|
||||
|
||||
if(params.slug !== 'users' && params.slug !== 'labels' && params.slug !== 'pullRequests') {
|
||||
if(params.slug !== 'users' && params.slug !== 'labels' && params.slug !== 'pullRequests' && params.slug !== 'issues') {
|
||||
return Response.json({ error: 'Invalid table name' }, { status: 400 });
|
||||
}
|
||||
|
||||
@ -21,6 +21,7 @@ interface PullRequestNode {
|
||||
id: string;
|
||||
title: string;
|
||||
body: string;
|
||||
url: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
closedAt: string;
|
||||
@ -35,6 +36,7 @@ interface IssueNode {
|
||||
id: string;
|
||||
title: string;
|
||||
body: string;
|
||||
url: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
closedAt: string;
|
||||
@ -90,6 +92,7 @@ async function fetchData(cursor: string | null = null, isIssues: boolean = false
|
||||
id
|
||||
title
|
||||
body
|
||||
url
|
||||
createdAt
|
||||
updatedAt
|
||||
closedAt
|
||||
@ -119,6 +122,7 @@ async function fetchData(cursor: string | null = null, isIssues: boolean = false
|
||||
id
|
||||
title
|
||||
body
|
||||
url
|
||||
createdAt
|
||||
updatedAt
|
||||
closedAt
|
||||
@ -178,6 +182,7 @@ const initDb = () => {
|
||||
id TEXT PRIMARY KEY,
|
||||
title TEXT,
|
||||
body TEXT,
|
||||
url TEXT,
|
||||
createdAt TEXT,
|
||||
updatedAt TEXT,
|
||||
closedAt TEXT,
|
||||
@ -192,7 +197,8 @@ const initDb = () => {
|
||||
id TEXT PRIMARY KEY,
|
||||
title TEXT,
|
||||
body TEXT,
|
||||
createdAt TEXT,
|
||||
url TEXT,
|
||||
createdAt TEXT,
|
||||
updatedAt TEXT,
|
||||
closedAt TEXT,
|
||||
authorId TEXT,
|
||||
@ -249,10 +255,10 @@ export async function GET() {
|
||||
|
||||
const assignableUsers = await fetchAssignableUsers();
|
||||
const prs = await fetchData(lastPRCursor) as Array<PullRequestNode>;
|
||||
const issues = await fetchData(lastIssueCursor) as Array<IssueNode>;
|
||||
const issues = await fetchData(lastIssueCursor, true) as Array<IssueNode>;
|
||||
|
||||
const insertPR = db.prepare('INSERT INTO pullRequests (id, title, body, createdAt, updatedAt, closedAt, mergedAt, authorId) VALUES (?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(id) DO NOTHING');
|
||||
const insertIssue = db.prepare('INSERT INTO issues (id, title, body, createdAt, updatedAt, closedAt, authorId) VALUES (?, ?, ?, ?, ?, ?, ?) ON CONFLICT(id) DO NOTHING');
|
||||
const insertPR = db.prepare('INSERT INTO pullRequests (id, title, body, url, createdAt, updatedAt, closedAt, mergedAt, authorId) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(id) DO NOTHING');
|
||||
const insertIssue = db.prepare('INSERT INTO issues (id, title, body, url, createdAt, updatedAt, closedAt, authorId) VALUES (?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(id) DO NOTHING');
|
||||
const insertUser = db.prepare('INSERT INTO users (id, login, avatarUrl, url, isEmployee) VALUES (?, ?, ?, ?, ?) ON CONFLICT(id) DO NOTHING');
|
||||
const insertLabel = db.prepare('INSERT INTO labels (id, name, color, description) VALUES (?, ?, ?, ?) ON CONFLICT(id) DO NOTHING');
|
||||
const insertPullRequestLabel = db.prepare('INSERT INTO pullRequestLabels (pullRequestId, labelId) VALUES (?, ?)');
|
||||
@ -262,7 +268,7 @@ export async function GET() {
|
||||
console.log(pr);
|
||||
if(pr.author == null) { continue; }
|
||||
insertUser.run(pr.author.resourcePath, pr.author.login, pr.author.avatarUrl, pr.author.url, assignableUsers.has(pr.author.login) ? 1 : 0);
|
||||
insertPR.run(pr.id, pr.title, pr.body, pr.createdAt, pr.updatedAt, pr.closedAt, pr.mergedAt, pr.author.resourcePath);
|
||||
insertPR.run(pr.id, pr.title, pr.body, pr.url, pr.createdAt, pr.updatedAt, pr.closedAt, pr.mergedAt, pr.author.resourcePath);
|
||||
|
||||
for (const label of pr.labels.nodes) {
|
||||
insertLabel.run(label.id, label.name, label.color, label.description);
|
||||
@ -274,7 +280,7 @@ export async function GET() {
|
||||
if(issue.author == null) { continue; }
|
||||
insertUser.run(issue.author.resourcePath, issue.author.login, issue.author.avatarUrl, issue.author.url, assignableUsers.has(issue.author.login) ? 1 : 0);
|
||||
|
||||
insertIssue.run(issue.id, issue.title, issue.body, issue.createdAt, issue.updatedAt, issue.closedAt, issue.author.resourcePath);
|
||||
insertIssue.run(issue.id, issue.title, issue.body, issue.url, issue.createdAt, issue.updatedAt, issue.closedAt, issue.author.resourcePath);
|
||||
|
||||
for (const label of issue.labels.nodes) {
|
||||
insertLabel.run(label.id, label.name, label.color, label.description);
|
||||
|
||||
@ -26,12 +26,12 @@ const Contributors = async () => {
|
||||
ORDER BY
|
||||
pullRequestCount DESC;
|
||||
`).all() as Contributor[];
|
||||
|
||||
|
||||
db.close();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Top Contributors</h1>
|
||||
<AvatarGrid users={contributors} />
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user