From 2ff089eaa2367456435d2254c96c2d578b8f7d7d Mon Sep 17 00:00:00 2001 From: Noah Onyejese <129368606+noahonyejese@users.noreply.github.com> Date: Wed, 8 Jan 2025 14:50:23 +0100 Subject: [PATCH 1/2] feat: Added Github Project Syncronization --- app/api/github/events/projects/route.ts | 123 ++++++++++++++++-------- package-lock.json | 8 ++ package.json | 1 + server/github/syncs.ts | 30 ++++++ 4 files changed, 122 insertions(+), 40 deletions(-) create mode 100644 server/github/syncs.ts diff --git a/app/api/github/events/projects/route.ts b/app/api/github/events/projects/route.ts index 0693020..9978af7 100644 --- a/app/api/github/events/projects/route.ts +++ b/app/api/github/events/projects/route.ts @@ -1,5 +1,19 @@ import { verifyGitHubRequest } from '@/server/security'; import { createError } from '@/utils/error-handling'; +import { + CheckRunEvent, + CreateEvent, + DeleteEvent, + DeploymentEvent, + DeploymentStatusEvent, + PingEvent, + PullRequestEvent, + PullRequestReviewEvent, + PushEvent, + RepositoryEvent, + WebhookEventMap, + WebhookEventName, +} from '@octokit/webhooks-types'; import { NextRequest, NextResponse } from 'next/server'; export const POST = async (req: NextRequest) => { @@ -13,85 +27,114 @@ export const POST = async (req: NextRequest) => { }); } - const body = JSON.parse(rawBody); - const event = req.headers.get('x-github-event'); + const body = JSON.parse(rawBody) as WebhookEventMap[WebhookEventName]; + const event = req.headers.get('x-github-event') as WebhookEventName; switch (event) { - case 'ping': + case 'ping': { + const pingEvent = body as PingEvent; return NextResponse.json({ status: 'success', message: 'Pong!', + zen: pingEvent.zen, }); + } - case 'create': + case 'create': { + const createEvent = body as CreateEvent; console.log('Create event:', { - ref_type: body.ref_type, - ref: body.ref, - repository: body.repository.full_name, + ref_type: createEvent.ref_type, + ref: createEvent.ref, + repository: createEvent.repository.full_name, }); break; + } - case 'delete': + case 'delete': { + const deleteEvent = body as DeleteEvent; console.log('Delete event:', { - ref_type: body.ref_type, - ref: body.ref, - repository: body.repository.full_name, + ref_type: deleteEvent.ref_type, + ref: deleteEvent.ref, + repository: deleteEvent.repository.full_name, }); break; + } - case 'check_run': + case 'check_run': { + const checkRunEvent = body as CheckRunEvent; console.log('Check run event:', { - action: body.action, // created, completed, rerequested - status: body.check_run.status, - conclusion: body.check_run.conclusion, - name: body.check_run.name, + action: checkRunEvent.action, + status: checkRunEvent.check_run.status, + conclusion: checkRunEvent.check_run.conclusion, + name: checkRunEvent.check_run.name, }); break; + } - case 'deployment_status': - // Deployment status updates + case 'deployment_status': { + const deploymentStatusEvent = body as DeploymentStatusEvent; console.log('Deployment status event:', { - action: body.action, - state: body.deployment_status.state, - environment: body.deployment.environment, + action: deploymentStatusEvent.action, + state: deploymentStatusEvent.deployment_status.state, + environment: deploymentStatusEvent.deployment.environment, }); break; + } - case 'pull_request_review': - // PR review events + case 'pull_request_review': { + const pullRequestReviewEvent = body as PullRequestReviewEvent; console.log('PR review event:', { - action: body.action, - state: body.review.state, - pr_number: body.pull_request.number, + action: pullRequestReviewEvent.action, + state: pullRequestReviewEvent.review.state, + pr_number: pullRequestReviewEvent.pull_request.number, }); break; + } - case 'pull_request': + case 'pull_request': { + const pullRequestEvent = body as PullRequestEvent; console.log('Pull request event:', { - action: body.action, - number: body.pull_request.number, - state: body.pull_request.state, - merged: body.pull_request.merged, + action: pullRequestEvent.action, + number: pullRequestEvent.pull_request.number, + state: pullRequestEvent.pull_request.state, + merged: pullRequestEvent.pull_request.merged, }); break; + } - case 'push': + case 'push': { + const pushEvent = body as PushEvent; console.log('Push event:', { - ref: body.ref, - commits: body.commits.length, - repository: body.repository.full_name, + ref: pushEvent.ref, + commits: pushEvent.commits.length, + repository: pushEvent.repository.full_name, }); break; + } - case 'repository': + case 'repository': { + const repositoryEvent = body as RepositoryEvent; console.log('Repository event:', { - action: body.action, - repository: body.repository.full_name, + action: repositoryEvent.action, + repository: repositoryEvent.repository.full_name, }); break; + } + + case 'deployment': { + const deploymentEvent = body as DeploymentEvent; + console.log('Deployment event:', { + action: deploymentEvent.action, + environment: deploymentEvent.deployment.environment, + repository: deploymentEvent.repository.full_name, + }); + break; + } - default: - console.log(`Unhandled event type: ${event}`); + default: { + const eventName = event as WebhookEventName; + console.log(`Unhandled event type: ${eventName}`); + } } return NextResponse.json({ diff --git a/package-lock.json b/package-lock.json index b581bff..9407194 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "zod": "^3.23.8" }, "devDependencies": { + "@octokit/webhooks-types": "^7.6.1", "@types/animejs": "^3.1.12", "@types/d3": "^7.4.3", "@types/node": "^20", @@ -1154,6 +1155,13 @@ "node": ">=12" } }, + "node_modules/@octokit/webhooks-types": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/@octokit/webhooks-types/-/webhooks-types-7.6.1.tgz", + "integrity": "sha512-S8u2cJzklBC0FgTwWVLaM8tMrDuDMVE4xiTK4EYXM9GntyvrdbSoxqDQa+Fh57CCNApyIpyeqPhhFEmHPfrXgw==", + "dev": true, + "license": "MIT" + }, "node_modules/@opentelemetry/api": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", diff --git a/package.json b/package.json index 63cf6be..f2e99e1 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "zod": "^3.23.8" }, "devDependencies": { + "@octokit/webhooks-types": "^7.6.1", "@types/animejs": "^3.1.12", "@types/d3": "^7.4.3", "@types/node": "^20", diff --git a/server/github/syncs.ts b/server/github/syncs.ts new file mode 100644 index 0000000..92760bd --- /dev/null +++ b/server/github/syncs.ts @@ -0,0 +1,30 @@ +import { TeamProjectFirebase } from '@/types/firebase'; +import { Repository } from '@octokit/webhooks-types'; +import * as admin from 'firebase-admin'; + +export const projectSync = async (project: Repository) => { + const db = admin.database(); + const userRef = db.ref( + `data/team-projects/sensors/github/values/${project.id}` + ); + + const snapshot = await userRef.once('value'); + const projectData = snapshot.val() as TeamProjectFirebase; + + if (projectData) { + await userRef.update({ + ...projectData, + frequency: projectData.frequency + 1, + lastUpdated: Date.now(), + }); + } else { + await userRef.set({ + title: project.name, + logo: `https://github.com/${project.owner.id}/${project.id}/pinned-icon.svg`, + defaulBranch: project.default_branch, + masterBranch: project.master_branch, + frequency: 1, + lastUpdated: Date.now(), + }); + } +}; From 6ab67b277bb5b7896ad41c471e303c5e6e2b4489 Mon Sep 17 00:00:00 2001 From: Noah Onyejese <129368606+noahonyejese@users.noreply.github.com> Date: Wed, 8 Jan 2025 14:53:16 +0100 Subject: [PATCH 2/2] fix: Added Team Project Types --- types/data.ts | 3 +- types/firebase.ts | 104 +++++++++++++++++++++++++++++++++++++++++++++- utils/general.ts | 2 +- 3 files changed, 106 insertions(+), 3 deletions(-) diff --git a/types/data.ts b/types/data.ts index 68f4b65..50c2fbf 100644 --- a/types/data.ts +++ b/types/data.ts @@ -1,9 +1,10 @@ -import { NoiseLevelsData } from './firebase'; +import { NoiseLevelsData, TeamProject } from './firebase'; import { TeamMember } from './team-activity'; export interface FormtattedDataBaseType { 'team-communication': FormattedDataBase; 'noise-levels': FormattedDataBase; + 'team-projects': FormattedDataBase; } export interface FormattedDataBase { diff --git a/types/firebase.ts b/types/firebase.ts index 40881a1..42c9638 100644 --- a/types/firebase.ts +++ b/types/firebase.ts @@ -12,12 +12,13 @@ export interface Metric { } export interface Settings { - view_time: number; + [key: string]: number; } export interface Views { 'team-communication': View[]; 'noise-levels': View[]; + 'team-projects': View[]; } export interface View { @@ -39,6 +40,7 @@ export interface User { export interface DisplayDataType { 'team-communication': DisplayDataBase; 'noise-levels': DisplayDataBase; + 'team-projects': DisplayDataBase; } export interface DisplayDataBase { @@ -56,3 +58,103 @@ export interface DisplayDataBase { export interface NoiseLevelsData { db: number; } + +export interface TeamProjectFirebasesData { + [key: string]: TeamProjectFirebase; +} + +export interface TeamProject { + title: string; + logo: string; + pullRequests: PullRequest[]; + branches: Branch[]; + users: User[]; +} + +export interface TeamProjectFirebase { + title: string; + logo: string; + frequency: number; + lastUpdated: number; + masterBranch: string; + defaultBranch: string; + pullRequests: { + [key: string]: PullRequestFirebase; + }; + branches: { + [key: string]: BranchFirebase; + }; + users: { + [key: string]: User; + }; +} + +export interface PullRequest { + title: string; + author: string; + createdAt: number; + status: string; + sourceBranch: string; + targetBranch: string; + commits: Commit[]; + checks: Check[]; + timeline: GithubEvent[]; +} +export interface PullRequestFirebase { + title: string; + author: string; + createdAt: number; + status: string; + sourceBranch: string; + targetBranch: string; + commits: { + [key: string]: Commit; + }; + checks: { + [key: string]: Check; + }; + timeline: { + [key: string]: GithubEvent; + }; +} + +export interface Check { + name: string; + status: string; + timestamp: number; + type: string; +} + +export interface GithubEvent { + type: string; + timestamp: number; + actor: string; +} + +export interface Commit { + hash: string; + message: string; + timestamp: number; +} + +export interface BranchFirebase { + lastCommit: string; + lastUpdated: number; +} + +export interface Branch { + branch: string; + lastCommit: string; + lastUpdated: number; +} + +export interface User { + name: string; + email: string; + activePrs?: { + [key: string]: boolean; + }; + activeReviews?: { + [key: string]: boolean; + }; +} diff --git a/utils/general.ts b/utils/general.ts index 34077f4..3201910 100644 --- a/utils/general.ts +++ b/utils/general.ts @@ -120,7 +120,7 @@ export const viewTeamPositions = ({ const diagLength = space - margin * 2; - const outerOffset = 50; // Space between end and last point + const outerOffset = 50; const half = Math.ceil(members.length / 2); const positiveDiagonalMembers = members.slice(0, half);