From fa3a6b4afd263a21c1bef2916d8abb4d28e30013 Mon Sep 17 00:00:00 2001 From: Eddie Jaoude Date: Wed, 7 Aug 2024 05:55:58 +0100 Subject: [PATCH] feat: community checks (#101) * feat: community checks * fix: prisma formatting --- package.json | 2 +- .../20240806121744_community/migration.sql | 2 + prisma/schema.prisma | 37 ++++++++++--------- src/app/account/repo/checks/[id]/action.js | 5 ++- src/utils/checks/codeOfConduct.js | 22 +++++++++++ src/utils/checks/contributing.js | 22 +++++++++++ src/utils/checks/index.js | 12 ++++++ src/utils/checks/issueTemplates.js | 22 +++++++++++ src/utils/checks/license.js | 21 +++++++++++ src/utils/checks/pullRequestTemplate.js | 22 +++++++++++ src/utils/checks/readme.js | 21 +++++++++++ src/utils/github/getCommunityMetrics.js | 27 ++++++++++++++ src/utils/github/index.js | 6 ++- 13 files changed, 200 insertions(+), 21 deletions(-) create mode 100644 prisma/migrations/20240806121744_community/migration.sql create mode 100644 src/utils/checks/codeOfConduct.js create mode 100644 src/utils/checks/contributing.js create mode 100644 src/utils/checks/issueTemplates.js create mode 100644 src/utils/checks/license.js create mode 100644 src/utils/checks/pullRequestTemplate.js create mode 100644 src/utils/checks/readme.js create mode 100644 src/utils/github/getCommunityMetrics.js diff --git a/package.json b/package.json index 859b0b2..4603c74 100644 --- a/package.json +++ b/package.json @@ -16,9 +16,9 @@ "test": "npx playwright test", "prepare": "husky", "postinstall": "shx cp -n ./.env.example ./.env", - "db:migrate:dev": "npx prisma migrate dev", "format:write": "prettier . --write", "format:check": "prettier . --check", + "db:migrate:dev": "npx prisma migrate dev", "db:migrate:prod": "prisma migrate deploy" }, "dependencies": { diff --git a/prisma/migrations/20240806121744_community/migration.sql b/prisma/migrations/20240806121744_community/migration.sql new file mode 100644 index 0000000..92bbf5f --- /dev/null +++ b/prisma/migrations/20240806121744_community/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "GithubResponse" ADD COLUMN "communityMetrics" JSONB; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index b3a01e5..2001480 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -91,22 +91,23 @@ model Repository { repo String githubResponses GithubResponse[] - checks Check[] + checks Check[] userId String - user User @relation(fields: [userId], references: [id], onDelete: Cascade) + user User @relation(fields: [userId], references: [id], onDelete: Cascade) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model GithubResponse { - id String @id @default(cuid()) - repo Json - issues Json? - branches Json? - release Json? - - checks Check[] + id String @id @default(cuid()) + repo Json + issues Json? + branches Json? + release Json? + communityMetrics Json? + + checks Check[] repositoryId String repository Repository @relation(fields: [repositoryId], references: [id], onDelete: Cascade) @@ -115,15 +116,15 @@ model GithubResponse { } model Check { - id String @id @default(cuid()) - red Int - amber Int - green Int - healthchecks Json - data Json - - repositoryId String - repository Repository @relation(fields: [repositoryId], references: [id], onDelete: Cascade) + id String @id @default(cuid()) + red Int + amber Int + green Int + healthchecks Json + data Json + + repositoryId String + repository Repository @relation(fields: [repositoryId], references: [id], onDelete: Cascade) githubResponseId String githubResponse GithubResponse @relation(fields: [githubResponseId], references: [id], onDelete: Cascade) diff --git a/src/app/account/repo/checks/[id]/action.js b/src/app/account/repo/checks/[id]/action.js index 3ecbf55..1b175d9 100644 --- a/src/app/account/repo/checks/[id]/action.js +++ b/src/app/account/repo/checks/[id]/action.js @@ -33,7 +33,10 @@ export async function performChecks(formData) { // if github data is older than 24 hours fetch again let githubResponseRepo = repository.githubResponses[0]; - if (differenceInHours(new Date(), githubResponseRepo.createdAt) > 24) { + if ( + !githubResponseRepo || + differenceInHours(new Date(), githubResponseRepo.createdAt) > 24 + ) { const user = await prisma.user.findUnique({ where: { id: session.user.id }, include: { diff --git a/src/utils/checks/codeOfConduct.js b/src/utils/checks/codeOfConduct.js new file mode 100644 index 0000000..47df9b3 --- /dev/null +++ b/src/utils/checks/codeOfConduct.js @@ -0,0 +1,22 @@ +export default function codeOfConduct(communityMetrics) { + let response = { + id: "code-of-conduct", + href: "/repo/code-of-conduct", + title: "Code of Conduct", + }; + + if (communityMetrics.files.code_of_conduct) { + response.status = "success"; + response.description = `You have a CoC ${communityMetrics.files.code_of_conduct.name}.`; + response.extra = "No action required."; + } + + if (!communityMetrics.files.code_of_conduct) { + response.status = "error"; + response.description = "You do not have a CoC in your repo."; + response.extra = + "This is important for people to know your project and community is safe."; + } + + return response; +} diff --git a/src/utils/checks/contributing.js b/src/utils/checks/contributing.js new file mode 100644 index 0000000..b4c8f58 --- /dev/null +++ b/src/utils/checks/contributing.js @@ -0,0 +1,22 @@ +export default function contributing(communityMetrics) { + let response = { + id: "contributing", + href: "/repo/contributing", + title: "Contributing", + }; + + if (communityMetrics.files.contributing) { + response.status = "success"; + response.description = "You have a contributing guide."; + response.extra = "No action required."; + } + + if (!communityMetrics.files.contributing) { + response.status = "error"; + response.description = "You do not have a contributing guide in your repo."; + response.extra = + "This is important, so people know how to get started with your project."; + } + + return response; +} diff --git a/src/utils/checks/index.js b/src/utils/checks/index.js index 00034e4..18974e2 100644 --- a/src/utils/checks/index.js +++ b/src/utils/checks/index.js @@ -1,9 +1,15 @@ import activity from "./activity"; import branches from "./branches"; +import codeOfConduct from "./codeOfConduct"; +import contributing from "./contributing"; import defaultBranch from "./defaultBranch"; import description from "./description"; import goodFirstIssue from "./goodFirstIssue"; import issues from "./issues"; +import issueTemplates from "./issueTemplates"; +import license from "./license"; +import pullRequestTemplate from "./pullRequestTemplate"; +import readme from "./readme"; import release from "./release"; import topics from "./topics"; import url from "./url"; @@ -31,6 +37,12 @@ export default function checks(data) { goodFirstIssue(data.issues), branches(data.branches), release(data.release), + readme(data.communityMetrics), + license(data.communityMetrics), + contributing(data.communityMetrics), + // issueTemplates(data.communityMetrics), // TODO data from github json is always null + pullRequestTemplate(data.communityMetrics), + codeOfConduct(data.communityMetrics), ]; const summary = checkSummary(checks); diff --git a/src/utils/checks/issueTemplates.js b/src/utils/checks/issueTemplates.js new file mode 100644 index 0000000..b9b8c76 --- /dev/null +++ b/src/utils/checks/issueTemplates.js @@ -0,0 +1,22 @@ +export default function issueTemplates(communityMetrics) { + let response = { + id: "issue-templates", + href: "/repo/issue-templates", + title: "Issue templates", + }; + + if (communityMetrics.files.issue_template) { + response.status = "success"; + response.description = "You have issue templates."; + response.extra = "No action required."; + } + + if (!communityMetrics.files.issue_template) { + response.status = "error"; + response.description = "You do not have any issue templates in your repo."; + response.extra = + "This helps people create better issues, for example focused on a feature or bug."; + } + + return response; +} diff --git a/src/utils/checks/license.js b/src/utils/checks/license.js new file mode 100644 index 0000000..8067a0b --- /dev/null +++ b/src/utils/checks/license.js @@ -0,0 +1,21 @@ +export default function license(communityMetrics) { + let response = { + id: "license", + href: "/repo/license", + title: "License", + }; + + if (communityMetrics.files.license) { + response.status = "success"; + response.description = `You have a license ${communityMetrics.files.license.spdx_id}.`; + response.extra = "No action required."; + } + + if (!communityMetrics.files.license) { + response.status = "error"; + response.description = "You do not have a license in your repo."; + response.extra = "This does not mean it is moe Open Source but less."; + } + + return response; +} diff --git a/src/utils/checks/pullRequestTemplate.js b/src/utils/checks/pullRequestTemplate.js new file mode 100644 index 0000000..e9930d8 --- /dev/null +++ b/src/utils/checks/pullRequestTemplate.js @@ -0,0 +1,22 @@ +export default function pullRequestTemplate(communityMetrics) { + let response = { + id: "pull-request-template", + href: "/repo/pull-request-template", + title: "Pull Request template", + }; + + if (communityMetrics.files.pull_request_template) { + response.status = "success"; + response.description = "You have a Pull Request template."; + response.extra = "No action required."; + } + + if (!communityMetrics.files.pull_request_template) { + response.status = "error"; + response.description = + "You do not have a pull request template in your repo."; + response.extra = "This helps people create better pull requests."; + } + + return response; +} diff --git a/src/utils/checks/readme.js b/src/utils/checks/readme.js new file mode 100644 index 0000000..3d573da --- /dev/null +++ b/src/utils/checks/readme.js @@ -0,0 +1,21 @@ +export default function readme(communityMetrics) { + let response = { + id: "readme", + href: "/repo/readme", + title: "Readme", + }; + + if (communityMetrics.files.readme) { + response.status = "success"; + response.description = "You have a README file."; + response.extra = "No action required."; + } + + if (!communityMetrics.files.readme) { + response.status = "error"; + response.description = "You do not have a readme.md file in your repo."; + response.extra = "This is the most important file in your project."; + } + + return response; +} diff --git a/src/utils/github/getCommunityMetrics.js b/src/utils/github/getCommunityMetrics.js new file mode 100644 index 0000000..8b0af4c --- /dev/null +++ b/src/utils/github/getCommunityMetrics.js @@ -0,0 +1,27 @@ +import { Octokit } from "@octokit/rest"; +import extractOwnerRepo from "./extractOwnerRepo"; + +export default async function getCommunityMetricsApi(url, token) { + // get owner and repo name from url + const { owner, repo } = extractOwnerRepo(url); + + // get data from github api using user's API + const octokit = new Octokit({ + auth: token, + }); + let response; + try { + response = await octokit.rest.repos.getCommunityProfileMetrics({ + owner, + repo, + }); + } catch (e) { + console.error(e); + response = { + status: 404, + data: {}, + }; + } + console.log("-----", response); + return response; +} diff --git a/src/utils/github/index.js b/src/utils/github/index.js index ed5d11e..95cec9a 100644 --- a/src/utils/github/index.js +++ b/src/utils/github/index.js @@ -1,13 +1,17 @@ import getBranchesApi from "./getBranchesApi"; +import getCommunityMetricsApi from "./getCommunityMetrics"; import getIssuesApi from "./getIssuesApi"; import getReleaseApi from "./getReleaseApi"; import getRepoApi from "./getRepoApi"; export default async function getAllRepoData(url, token) { + const repo = (await getRepoApi(url, token)).data; + return { - repo: (await getRepoApi(url, token)).data, + repo: repo, issues: (await getIssuesApi(url, token)).data, branches: (await getBranchesApi(url, token)).data, release: (await getReleaseApi(url, token)).data, + communityMetrics: (await getCommunityMetricsApi(url, token)).data, }; }