diff --git a/backend/app.ts b/backend/app.ts index beedd80f..cc84d245 100644 --- a/backend/app.ts +++ b/backend/app.ts @@ -21,8 +21,8 @@ import submissionRoutes from './routes/submissionRoutes'; import assessmentAssignmentSetRoutes from './routes/assessmentAssignmentSetRoutes'; import assessmentResultRoutes from './routes/assessmentResultRoutes'; import notificationRoutes from './routes/notificationRoutes'; -import setupNotificationJob from 'jobs/notificationJob'; -import setupTutorialDataJob from 'jobs/tutorialDataJob'; +import setupTutorialDataJob from './jobs/tutorialDataJob'; +import setupNotificationJob from './jobs/notificationJob'; const env = process.env.NODE_ENV ?? 'development'; config({ path: `.env.${env}` }); diff --git a/backend/jobs/codeAnalysisJob.ts b/backend/jobs/codeAnalysisJob.ts index a216f321..b289fd53 100644 --- a/backend/jobs/codeAnalysisJob.ts +++ b/backend/jobs/codeAnalysisJob.ts @@ -512,6 +512,7 @@ const getMedianAndMeanCodeData = async (course: any) => { const codeAnalysisData = await codeAnalysisDataModel.find({ gitHubOrgName: course.gitHubOrgName, + repoName: { $regex: `^${course.repoNameFilter}` }, executionTime: { $gte: startOfDay, $lte: endOfDay, @@ -519,7 +520,7 @@ const getMedianAndMeanCodeData = async (course: any) => { }); console.log( - `Getting mean and median code analysis values for ${course.gitHubOrgName} - ${codeAnalysisData.length} records` + `Getting mean and median code analysis values for ${course.gitHubOrgName}, ${course.repoNameFilter} - ${codeAnalysisData.length} records` ); if (codeAnalysisData.length === 0) return; diff --git a/backend/jobs/githubJob.ts b/backend/jobs/githubJob.ts index 3ed83361..398bb64d 100644 --- a/backend/jobs/githubJob.ts +++ b/backend/jobs/githubJob.ts @@ -9,7 +9,7 @@ import { Review, TeamContribution, TeamPR } from '@shared/types/TeamData'; import cron from 'node-cron'; import { App, Octokit } from 'octokit'; import TeamData from '../models/TeamData'; -import { getGitHubApp, getTeamMembers } from '../utils/github'; +import { getGitHubApp } from '../utils/github'; const fetchAndSaveTeamData = async () => { const app: App = getGitHubApp(); @@ -238,6 +238,7 @@ const getCourseData = async (octokit: Octokit, course: any) => { } const teamData = { + course: course._id, gitHubOrgName: gitHubOrgName.toLowerCase(), teamId: repo.id, repoName: repo.name, diff --git a/backend/jobs/notificationJob.ts b/backend/jobs/notificationJob.ts index f1d2f9f3..1a1541d5 100644 --- a/backend/jobs/notificationJob.ts +++ b/backend/jobs/notificationJob.ts @@ -10,7 +10,7 @@ import { User } from '../models/User'; import InternalAssessmentModel, { InternalAssessment, } from '@models/InternalAssessment'; -import { getUnmarkedAssignmentsByTAId } from 'services/assessmentAssignmentSetService'; +import { getUnmarkedAssignmentsByTAId } from '../services/assessmentAssignmentSetService'; import { sendTelegramMessage } from '../clients/telegramClient'; /** diff --git a/backend/jobs/publicGithubJob.ts b/backend/jobs/publicGithubJob.ts index 6e91e530..3d37f6d8 100644 --- a/backend/jobs/publicGithubJob.ts +++ b/backend/jobs/publicGithubJob.ts @@ -224,6 +224,7 @@ const getPublicCourseData = async (course: any) => { const teamData = { gitHubOrgName: owner.toLowerCase(), + course: course._id, teamId: repoData.data.id, repoName: repo, commits: commits.length, diff --git a/backend/models/TeamData.ts b/backend/models/TeamData.ts index 1c24f147..80facd45 100644 --- a/backend/models/TeamData.ts +++ b/backend/models/TeamData.ts @@ -78,6 +78,7 @@ const milestoneSchema = new Schema( const teamDataSchema = new Schema({ teamId: { type: Number, required: true }, + course: { type: Schema.Types.ObjectId, ref: 'Course', required: true }, gitHubOrgName: { type: String, required: true }, repoName: { type: String, required: true }, commits: { type: Number, required: true }, diff --git a/backend/services/githubService.ts b/backend/services/githubService.ts index f1da3428..e12af3b4 100644 --- a/backend/services/githubService.ts +++ b/backend/services/githubService.ts @@ -74,18 +74,8 @@ export const getAuthorizedTeamDataByCourse = async ( throw new NotFoundError('User is not authorized to view course'); } - // Extract the owner names from the course's GitHub repo links - const ownersFromRepoLinks = (course.gitHubRepoLinks || []).map(repoUrl => { - const urlParts = (repoUrl as string).split('/'); - return urlParts[3].toLowerCase(); // Get the 'owner' part of the URL in lowercase - }); - - // Query for team data based on gitHubOrgName or gitHubRepoLinks const teamDatas = await TeamDataModel.find({ - $or: [ - { gitHubOrgName: course.gitHubOrgName }, - { gitHubOrgName: { $in: ownersFromRepoLinks } }, - ], + course: courseId, }); if (!teamDatas) { diff --git a/backend/test/services/codeAnalysisService.test.ts b/backend/test/services/codeAnalysisService.test.ts index 3f2ea670..55c940c5 100644 --- a/backend/test/services/codeAnalysisService.test.ts +++ b/backend/test/services/codeAnalysisService.test.ts @@ -131,6 +131,7 @@ describe('codeAnalysisService', () => { const teamData1 = new TeamDataModel({ teamId: 1, gitHubOrgName: 'org', + course: mockCourse._id, repoName: 'team1', teamContributions: {}, teamPRs: [], @@ -146,6 +147,7 @@ describe('codeAnalysisService', () => { const teamData2 = new TeamDataModel({ teamId: 2, gitHubOrgName: 'org', + course: mockCourse._id, repoName: 'team2', teamContributions: {}, teamPRs: [], @@ -468,6 +470,7 @@ describe('codeAnalysisService', () => { const teamDataA = new TeamDataModel({ teamId: 123, gitHubOrgName: 'NoCodeAnalysisDataOrg', + course: courseWithoutCodeAnalysisData._id, repoName: 'teamA', teamContributions: {}, teamPRs: [], diff --git a/backend/test/services/githubService.test.ts b/backend/test/services/githubService.test.ts index b344be3d..aeee0406 100644 --- a/backend/test/services/githubService.test.ts +++ b/backend/test/services/githubService.test.ts @@ -79,7 +79,7 @@ describe('gitHubService', () => { const teamData1 = new TeamDataModel({ repoName: 'team1', gitHubOrgName: 'org', - courseId: mockFacultyCourseId, + course: mockCourse._id, teamContributions: [], pullRequests: 0, issues: 0, @@ -89,7 +89,7 @@ describe('gitHubService', () => { const teamData2 = new TeamDataModel({ repoName: 'team2', gitHubOrgName: 'org', - courseId: mockFacultyCourseId, + course: mockCourse._id, teamContributions: [], pullRequests: 0, issues: 0, diff --git a/multi-git-dashboard/src/components/forms/ConnectTrofosForm.tsx b/multi-git-dashboard/src/components/forms/ConnectTrofosForm.tsx index 9f456b96..3d048eb7 100644 --- a/multi-git-dashboard/src/components/forms/ConnectTrofosForm.tsx +++ b/multi-git-dashboard/src/components/forms/ConnectTrofosForm.tsx @@ -29,8 +29,9 @@ const ConnectTrofosForm = ({ // Optional: Add validation for the fields validate: { - apiKey: value => (value.length === 0 ? 'API key is required' : null), - trofosCourseId: value => + apiKey: (value: string) => + value.length === 0 ? 'API key is required' : null, + trofosCourseId: (value: number) => value < 0 ? 'This is not a valid Course ID' : null, }, }); diff --git a/multi-git-dashboard/src/components/forms/CreateCourseForm.tsx b/multi-git-dashboard/src/components/forms/CreateCourseForm.tsx index 6981ebf9..4b742c89 100644 --- a/multi-git-dashboard/src/components/forms/CreateCourseForm.tsx +++ b/multi-git-dashboard/src/components/forms/CreateCourseForm.tsx @@ -66,23 +66,25 @@ const CreateCourse: React.FC = () => { installationId: '', }, validate: { - name: value => + name: (value: string) => value.trim().length > 0 ? null : 'Course name is required', - code: value => + code: (value: string) => value.trim().length > 0 ? null : 'Course code is required', - semester: value => + semester: (value: string) => value.trim().length > 0 ? null : 'Semester is required', - startDate: value => (value ? null : 'Start date is required'), - duration: value => (value ? null : 'Duration is required'), - courseType: value => (value ? null : 'Course type is required'), + startDate: (value: Date | null) => + value ? null : 'Start date is required', + duration: (value: number) => (value ? null : 'Duration is required'), + courseType: (value: CourseType) => + value ? null : 'Course type is required', // field should be valid only if courseType is Normal, or if courseType is GitHubOrg and installation check is successful - gitHubOrgName: (value, values) => + gitHubOrgName: (value: string, values: CreateCourseFormValues) => values.courseType === CourseType.Normal || (values.courseType === CourseType.GitHubOrg && appInstallationStatus === InstallationStatus.SUCCESS) ? null : 'GitHub Org name is required', - repoNameFilter: (value, values) => + repoNameFilter: (value: string, values: CreateCourseFormValues) => values.courseType === CourseType.Normal || (values.courseType === CourseType.GitHubOrg && appInstallationStatus === InstallationStatus.SUCCESS) diff --git a/multi-git-dashboard/src/components/forms/MilestoneForm.tsx b/multi-git-dashboard/src/components/forms/MilestoneForm.tsx index 677fd789..f5c8954b 100644 --- a/multi-git-dashboard/src/components/forms/MilestoneForm.tsx +++ b/multi-git-dashboard/src/components/forms/MilestoneForm.tsx @@ -19,9 +19,9 @@ const MilestoneForm: React.FC = ({ description: '', }, validate: { - number: value => + number: (value: number) => value >= 1 && value <= 100 ? null : 'Invalid milestone number', - dateline: value => (value ? null : 'Dateline is required'), + dateline: (value: Date) => (value ? null : 'Dateline is required'), }, }); diff --git a/multi-git-dashboard/src/components/forms/SprintForm.tsx b/multi-git-dashboard/src/components/forms/SprintForm.tsx index 3e772d53..803ac4ce 100644 --- a/multi-git-dashboard/src/components/forms/SprintForm.tsx +++ b/multi-git-dashboard/src/components/forms/SprintForm.tsx @@ -20,10 +20,10 @@ const SprintForm: React.FC = ({ endDate: new Date(), }, validate: { - number: value => + number: (value: number) => value >= 1 && value <= 100 ? null : 'Invalid sprint number', - startDate: value => (value ? null : 'Start date is required'), - endDate: value => (value ? null : 'End date is required'), + startDate: (value: Date) => (value ? null : 'Start date is required'), + endDate: (value: Date) => (value ? null : 'End date is required'), }, }); diff --git a/multi-git-dashboard/src/pages/auth/register.tsx b/multi-git-dashboard/src/pages/auth/register.tsx index f552c150..e0c68a47 100644 --- a/multi-git-dashboard/src/pages/auth/register.tsx +++ b/multi-git-dashboard/src/pages/auth/register.tsx @@ -39,20 +39,21 @@ const RegisterPage: React.FC = () => { role: Roles.TA, }, validate: { - identifier: value => + identifier: (value: string) => value.trim().length < 3 ? 'NUSNet ID must be at least 3 characters long' : null, - name: value => + name: (value: string) => value.trim().length < 3 ? 'Name must be at least 3 characters long' : null, - email: value => (!/^\S+@\S+$/.test(value) ? 'Invalid email' : null), - password: value => + email: (value: string) => + !/^\S+@\S+$/.test(value) ? 'Invalid email' : null, + password: (value: string) => value.length < 6 ? 'Password must be at least 6 characters long' : null, - confirmPassword: (value, values) => + confirmPassword: (value: string, values: FormValues) => value !== values.password ? 'Passwords do not match' : null, - role: value => + role: (value: Role) => !Object.values(Roles).includes(value) ? 'Invalid role' : null, }, }); diff --git a/shared/types/TeamData.ts b/shared/types/TeamData.ts index 43131b1c..aed8e02c 100644 --- a/shared/types/TeamData.ts +++ b/shared/types/TeamData.ts @@ -1,3 +1,5 @@ +import { Course } from './Course'; + export interface TeamContribution { commits: number; createdIssues: number; @@ -49,6 +51,7 @@ export interface Milestone { export interface TeamData { _id: string; + course: Course; gitHubOrgName: string; teamId: number; repoName: string;