New folder structure for website (#4159)

New folder structure
This commit is contained in:
Félix Malfait
2024-02-23 17:42:13 +01:00
committed by GitHub
parent 06c4665a44
commit 5de1c2c31d
65 changed files with 128 additions and 537 deletions

View File

@ -0,0 +1,134 @@
export const dynamic = 'force-dynamic';
import { Metadata } from 'next';
import { ActivityLog } from '@/app/_components/contributors/ActivityLog';
import { Breadcrumb } from '@/app/_components/contributors/Breadcrumb';
import { ContentContainer } from '@/app/_components/contributors/ContentContainer';
import { ProfileCard } from '@/app/_components/contributors/ProfileCard';
import { ProfileInfo } from '@/app/_components/contributors/ProfileInfo';
import { PullRequests } from '@/app/_components/contributors/PullRequests';
import { ThankYou } from '@/app/_components/contributors/ThankYou';
import { Background } from '@/app/_components/oss-friends/Background';
import { findAll } from '@/database/database';
import { pullRequestModel, userModel } from '@/database/model';
export function generateMetadata({
params,
}: {
params: { slug: string };
}): Metadata {
return {
title: params.slug + ' | Contributors',
};
}
export default async function ({ params }: { params: { slug: string } }) {
const contributors = await findAll(userModel);
const contributor = contributors.find(
(contributor) => contributor.id === params.slug,
);
if (!contributor) {
return;
}
const pullRequests = await findAll(pullRequestModel);
const mergedPullRequests = pullRequests
.filter((pr) => pr.mergedAt !== null)
.filter(
(pr) =>
![
'dependabot',
'cyborch',
'emilienchvt',
'Samox',
'charlesBochet',
'gitstart-app',
'thaisguigon',
'lucasbordeau',
'magrinj',
'Weiko',
'gitstart-twenty',
'bosiraphael',
'martmull',
'FelixMalfait',
'thomtrp',
'Bonapara',
'nimraahmed',
].includes(pr.authorId),
);
const contributorPullRequests = pullRequests.filter(
(pr) => pr.authorId === contributor.id,
);
const mergedContributorPullRequests = contributorPullRequests.filter(
(pr) => pr.mergedAt !== null,
);
const mergedContributorPullRequestsByContributor = mergedPullRequests.reduce(
(acc, pr) => {
acc[pr.authorId] = (acc[pr.authorId] || 0) + 1;
return acc;
},
{},
);
const mergedContributorPullRequestsByContributorArray = Object.entries(
mergedContributorPullRequestsByContributor,
)
.map(([authorId, value]) => ({ authorId, value }))
.sort((a, b) => b.value - a.value);
const contributorRank =
((mergedContributorPullRequestsByContributorArray.findIndex(
(contributor) => contributor.authorId === params.slug,
) +
1) /
contributors.length) *
100;
const pullRequestActivity = contributorPullRequests.reduce((acc, pr) => {
const date = new Date(pr.createdAt).toISOString().split('T')[0];
acc[date] = (acc[date] || 0) + 1;
return acc;
}, []);
const pullRequestActivityArray = Object.entries(pullRequestActivity)
.map(([day, value]) => ({ day, value }))
.sort((a, b) => new Date(a.day).getTime() - new Date(b.day).getTime());
return (
<>
<Background />
<ContentContainer>
<Breadcrumb active={contributor.id} />
<ProfileCard
username={contributor.id}
avatarUrl={contributor.avatarUrl}
firstContributionAt={pullRequestActivityArray[0]?.day}
/>
<ProfileInfo
mergedPRsCount={mergedContributorPullRequests.length}
rank={Math.ceil(Number(contributorRank)).toFixed(0)}
activeDays={pullRequestActivityArray.length}
/>
<ActivityLog data={pullRequestActivityArray} />
<PullRequests
list={
contributorPullRequests.slice(0, 9) as {
id: string;
title: string;
url: string;
createdAt: string;
mergedAt: string | null;
authorId: string;
}[]
}
/>
<ThankYou authorId={contributor.login} />
</ContentContainer>
</>
);
}

View File

@ -0,0 +1,335 @@
export const dynamic = 'force-dynamic';
import { global } from '@apollo/client/utilities/globals';
import { graphql } from '@octokit/graphql';
import { insertMany, migrate } from '@/database/database';
import {
issueLabelModel,
issueModel,
labelModel,
pullRequestLabelModel,
pullRequestModel,
userModel,
} from '@/database/model';
interface LabelNode {
id: string;
name: string;
color: string;
description: string;
}
export interface AuthorNode {
resourcePath: string;
login: string;
avatarUrl: string;
url: string;
}
interface PullRequestNode {
id: string;
title: string;
body: string;
url: string;
createdAt: string;
updatedAt: string;
closedAt: string;
mergedAt: string;
author: AuthorNode;
labels: {
nodes: LabelNode[];
};
}
interface IssueNode {
id: string;
title: string;
body: string;
url: string;
createdAt: string;
updatedAt: string;
closedAt: string;
author: AuthorNode;
labels: {
nodes: LabelNode[];
};
}
interface PageInfo {
hasNextPage: boolean;
endCursor: string | null;
}
interface PullRequests {
nodes: PullRequestNode[];
pageInfo: PageInfo;
}
interface Issues {
nodes: IssueNode[];
pageInfo: PageInfo;
}
interface AssignableUserNode {
login: string;
}
interface AssignableUsers {
nodes: AssignableUserNode[];
}
interface RepoData {
repository: {
pullRequests: PullRequests;
issues: Issues;
assignableUsers: AssignableUsers;
};
}
const query = graphql.defaults({
headers: {
Authorization: 'bearer ' + global.process.env.GITHUB_TOKEN,
},
});
async function fetchData(
cursor: string | null = null,
isIssues: boolean = false,
accumulatedData: Array<PullRequestNode | IssueNode> = [],
): Promise<Array<PullRequestNode | IssueNode>> {
const { repository } = await query<RepoData>(
`
query ($cursor: String) {
repository(owner: "twentyhq", name: "twenty") {
pullRequests(first: 100, after: $cursor, orderBy: {field: CREATED_AT, direction: DESC}) @skip(if: ${isIssues}) {
nodes {
id
title
body
url
createdAt
updatedAt
closedAt
mergedAt
author {
resourcePath
login
avatarUrl(size: 460)
url
}
labels(first: 100) {
nodes {
id
name
color
description
}
}
}
pageInfo {
hasNextPage
endCursor
}
}
issues(first: 100, after: $cursor, orderBy: {field: CREATED_AT, direction: DESC}) @skip(if: ${!isIssues}) {
nodes {
id
title
body
url
createdAt
updatedAt
closedAt
author {
resourcePath
login
avatarUrl
url
}
labels(first: 100) {
nodes {
id
name
color
description
}
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
`,
{ cursor },
);
const newAccumulatedData: Array<PullRequestNode | IssueNode> = [
...accumulatedData,
...(isIssues ? repository.issues.nodes : repository.pullRequests.nodes),
];
const pageInfo = isIssues
? repository.issues.pageInfo
: repository.pullRequests.pageInfo;
if (pageInfo.hasNextPage) {
return fetchData(pageInfo.endCursor, isIssues, newAccumulatedData);
} else {
return newAccumulatedData;
}
}
async function fetchAssignableUsers(): Promise<Set<string>> {
const { repository } = await query<RepoData>(`
query {
repository(owner: "twentyhq", name: "twenty") {
assignableUsers(first: 100) {
nodes {
login
}
}
}
}
`);
return new Set(repository.assignableUsers.nodes.map((user) => user.login));
}
export async function GET() {
if (!global.process.env.GITHUB_TOKEN) {
return new Response('No GitHub token provided', { status: 500 });
}
await migrate();
// TODO if we ever hit API Rate Limiting
const lastPRCursor = null;
const lastIssueCursor = null;
const assignableUsers = await fetchAssignableUsers();
const fetchedPRs = (await fetchData(lastPRCursor)) as Array<PullRequestNode>;
const fetchedIssues = (await fetchData(
lastIssueCursor,
true,
)) as Array<IssueNode>;
for (const pr of fetchedPRs) {
if (pr.author == null) {
continue;
}
await insertMany(
userModel,
[
{
id: pr.author.login,
avatarUrl: pr.author.avatarUrl,
url: pr.author.url,
isEmployee: assignableUsers.has(pr.author.login) ? '1' : '0',
},
],
{ onConflictKey: 'id' },
);
await insertMany(
pullRequestModel,
[
{
id: pr.id,
title: pr.title,
body: pr.body,
url: pr.url,
createdAt: pr.createdAt,
updatedAt: pr.updatedAt,
closedAt: pr.closedAt,
mergedAt: pr.mergedAt,
authorId: pr.author.login,
},
],
{ onConflictKey: 'id' },
);
for (const label of pr.labels.nodes) {
await insertMany(
labelModel,
[
{
id: label.id,
name: label.name,
color: label.color,
description: label.description,
},
],
{ onConflictKey: 'id' },
);
await insertMany(pullRequestLabelModel, [
{
pullRequestId: pr.id,
labelId: label.id,
},
]);
}
}
for (const issue of fetchedIssues) {
if (issue.author == null) {
continue;
}
await insertMany(
userModel,
[
{
id: issue.author.login,
avatarUrl: issue.author.avatarUrl,
url: issue.author.url,
isEmployee: assignableUsers.has(issue.author.login) ? '1' : '0',
},
],
{ onConflictKey: 'id' },
);
await insertMany(
issueModel,
[
{
id: issue.id,
title: issue.title,
body: issue.body,
url: issue.url,
createdAt: issue.createdAt,
updatedAt: issue.updatedAt,
closedAt: issue.closedAt,
authorId: issue.author.login,
},
],
{ onConflictKey: 'id' },
);
for (const label of issue.labels.nodes) {
await insertMany(
labelModel,
[
{
id: label.id,
name: label.name,
color: label.color,
description: label.description,
},
],
{ onConflictKey: 'id' },
);
await insertMany(issueLabelModel, [
{
pullRequestId: issue.id,
labelId: label.id,
},
]);
}
}
return new Response('Data synced', {
status: 200,
});
}

View File

@ -0,0 +1,58 @@
export const dynamic = 'force-dynamic';
import AvatarGrid from '@/app/_components/contributors/AvatarGrid';
import { Header } from '@/app/_components/contributors/Header';
import { Background } from '@/app/_components/oss-friends/Background';
import { ContentContainer } from '@/app/_components/oss-friends/ContentContainer';
import { findAll } from '@/database/database';
import { pullRequestModel, userModel } from '@/database/model';
interface Contributor {
id: string;
avatarUrl: string;
}
const Contributors = async () => {
const contributors = await findAll(userModel);
const pullRequests = await findAll(pullRequestModel);
const pullRequestByAuthor = pullRequests.reduce((acc, pr) => {
acc[pr.authorId] = acc[pr.authorId] ? acc[pr.authorId] + 1 : 1;
return acc;
}, {});
const fitlerContributors = contributors
.filter((contributor) => contributor.isEmployee === '0')
.filter(
(contributor) =>
![
'dependabot',
'cyborch',
'emilienchvt',
'Samox',
'nimraahmed',
'gitstart-app',
].includes(contributor.id),
)
.map((contributor) => {
contributor.pullRequestCount = pullRequestByAuthor[contributor.id] || 0;
return contributor;
})
.sort((a, b) => b.pullRequestCount - a.pullRequestCount)
.filter((contributor) => contributor.pullRequestCount > 0);
return (
<>
<Background />
<ContentContainer>
<Header />
<div>
<AvatarGrid users={fitlerContributors as Contributor[]} />
</div>
</ContentContainer>
</>
);
};
export default Contributors;