Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GitHubRepoURL value type #65

Merged
merged 3 commits into from
Feb 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions client/src/components/Projects/CodeActivity.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { useLocation, useNavigate } from "react-router-dom";
import ReturnButton from "../Components/return";
import { Octokit } from "@octokit/rest";
import { Endpoints } from "@octokit/types";
import "./CodeActivity.css";
import {
LineChart,
Expand All @@ -14,11 +15,20 @@
ResponsiveContainer,
} from "recharts";

type ArrayElement<T> = T extends (infer U)[] ? U : never;
type Commit = ArrayElement<Endpoints["GET /repos/{owner}/{repo}/commits"]["response"]["data"]>;
type Sprint = {
id: number,

Check failure on line 21 in client/src/components/Projects/CodeActivity.tsx

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
projectGroupName: string,
sprintName: string,
endDate: number,
};

const CodeActivity: React.FC = () => {
const navigate = useNavigate();
const location = useLocation();

const [commits, setCommits] = useState<any[]>([]);
const [commits, setCommits] = useState<Commit[]>([]);

Check failure on line 31 in client/src/components/Projects/CodeActivity.tsx

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
// GitHub API only returns 30 results on subsequent requests
const [loading, setLoading] = useState<boolean>(true);
const [page, setPage] = useState<number>(1);
Expand All @@ -27,8 +37,8 @@
const [repoDetails, setRepoDetails] = useState<{
owner: string;
repo: string;
} | null>(null);

Check failure on line 40 in client/src/components/Projects/CodeActivity.tsx

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
const [sprints, setSprints] = useState<any[]>([]);

Check failure on line 41 in client/src/components/Projects/CodeActivity.tsx

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type

const [projectName, setProjectName] = useState<string | null>("");
const [user, setUser] = useState<{
Expand All @@ -37,7 +47,7 @@
githubUsername: string;
} | null>(null);
const [selectedProjectGroup, setSelectedProjectGroup] = useState<string>("");
const [commitsPerSprint, setCommitsPerSprint] = useState<any[]>([]);

Check failure on line 50 in client/src/components/Projects/CodeActivity.tsx

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type

const handleNavigation = () => {
navigate("/code-activity");
Expand Down Expand Up @@ -151,17 +161,17 @@
const fetchAllSprints = async () => {
if (!selectedProjectGroup) return;

try {

Check failure on line 164 in client/src/components/Projects/CodeActivity.tsx

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
const response = await fetch(
`http://localhost:3000/sprints?projectGroupName=${encodeURIComponent(
selectedProjectGroup
)}`
);
const fetchedSprints = await response.json();
const fetchedSprints: Sprint[] = await response.json();

// Only have end date, so calculate start date
const updatedSprints = fetchedSprints.map(
(sprint: any, index: number) => {
(sprint, index) => {
const sprintName = `sprint${index}`;
if (index === 0) {
// First sprint: start date is one week before end date
Expand Down Expand Up @@ -211,7 +221,7 @@

console.log("Fetched commits:", response.data);

const filteredCommits = response.data.filter((commit) => {
const filteredCommits: Commit[] = response.data.filter((commit) => {
const commitDate = commit.commit.author?.date
? new Date(commit.commit.author.date)
: new Date();
Expand Down Expand Up @@ -269,7 +279,7 @@
const sprintStart = new Date(sprint.startDate);
const sprintEnd = new Date(sprint.endDate);
const commitsInSprint = commits.filter((commit) => {
const commitDate = new Date(commit.commit.author.date);
const commitDate = new Date(commit.commit.author?.date ?? 0);
return commitDate >= sprintStart && commitDate <= sprintEnd;
});
return { sprint: sprint.name, count: commitsInSprint.length }; // Ensure `sprint` is the name
Expand Down
35 changes: 18 additions & 17 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"lint": "npm run lint --prefix client && npm run lint --prefix server"
},
"devDependencies": {
"@octokit/types": "^13.8.0",
"@typescript-eslint/eslint-plugin": "^8.18.2",
"@typescript-eslint/parser": "^8.18.2",
"concurrently": "^9.1.0",
Expand Down
2 changes: 1 addition & 1 deletion server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"scripts": {
"dev": "nodemon --watch src --ext .ts,.js --exec \"npm run build && node dist/server.js\"",
"build": "tsc",
"test":"vitest run tests/semester.test.ts --reporter=verbose",
"test":"vitest run tests/ --reporter=verbose",
"lint": "eslint . --report-unused-disable-directives --max-warnings 0"
},
"dependencies": {
Expand Down
3 changes: 2 additions & 1 deletion server/src/Models/CourseProject.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Email } from "../email";
import { GitHubRepoURL } from "./GitHubRepoURL";

export class CourseProject {

public userEmail: Email = new Email("");
public projectName: string = "";
public url: string = "";
public gitHubRepoURL: GitHubRepoURL = new GitHubRepoURL("");

constructor() {
// dummy course project for set impl
Expand Down
63 changes: 63 additions & 0 deletions server/src/Models/GitHubRepoURL.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { URL } from "node:url";
import { IllegalArgumentException } from "../Exceptions/IllegalArgumentException";


export class GitHubRepoURL {
private readonly url: URL;

/**
* Value type for GitHub URLs. Only http:// and https:// URLs are supported.
*
* @param input - The URL to be parsed as a string
* @returns The value type object
*/
constructor(input: string) {
const url = URL.parse(input);
if (!url)
throw new IllegalArgumentException("Invalid URL");
this.url = url;

if (!this.isGithubUrl())
throw new IllegalArgumentException("Not a GitHub URL");

if (!this.isValidProtocol())
throw new IllegalArgumentException("Unsupported protocol");

if (!this.isRepo())
throw new IllegalArgumentException("The URL has no repository structure");
}

/**
* @returns The whole URL as a string
*/
public asString() {
return this.url.href;
}

/**
* Checks if the URL hostname is github.com
* @returns True if the check succeeds, else false
*/
private isGithubUrl() {
return this.url.hostname === "github.com" ||
this.url.hostname === "www.github.com";
}

/**
* Checks if the URL protocol is http or https
* @returns True if the check succeeds, else false
*/
private isValidProtocol() {
return this.url.protocol === "http:" ||
this.url.protocol === "https:";
}

/**
* Checks if the URL pathname contains exactly two slashes with something
* behind them
* @returns True if the check succeeds, else false
*/
private isRepo() {
return (this.url.pathname.match(/\/./g) || []).length === 2;
}
}
29 changes: 29 additions & 0 deletions server/src/tests/gitHubRepoURL.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { describe, it, expect } from "vitest";
import { GitHubRepoURL } from "../Models/GitHubRepoURL";

describe("GitHubRepoURL", () => {
it("should create a new value", () => {
expect(new GitHubRepoURL("https://github.com/riehlegroup/mini-meco"));
});

it("asString() should return the whole URL", () => {
const url = new GitHubRepoURL("https://github.com:73/riehlegroup/mini-meco.git");
expect(url.asString()).toBe("https://github.com:73/riehlegroup/mini-meco.git");
});

it("should throw if URL isn't a GitHub URL", () => {
expect(() => new GitHubRepoURL("https://gitlab.com/riehlegroup/mini-meco")).toThrow();
});

it("should throw if URL isn't of supported structure", () => {
expect(() => new GitHubRepoURL("[email protected]:riehlegroup/mini-meco.git")).toThrow();
});

it("should throw if URL has unsupported protocoll", () => {
expect(() => new GitHubRepoURL("ssh://github.com/riehlegroup/mini-meco")).toThrow();
});

it("should throw if URL has no repo structure", () => {
expect(() => new GitHubRepoURL("https://github.com/riehlegroup")).toThrow();
});
});
4 changes: 2 additions & 2 deletions server/src/userStatus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ export class UserStatus {
// Define valid transitions between states
private static validTransitions: Record<UserStatusEnum, UserStatusEnum[]> = {
[UserStatusEnum.confirmed]: [UserStatusEnum.suspended],
[UserStatusEnum.unconfirmed]: [UserStatusEnum.confirmed, UserStatusEnum.suspended],
[UserStatusEnum.suspended]: [UserStatusEnum.confirmed],
[UserStatusEnum.unconfirmed]: [UserStatusEnum.confirmed, UserStatusEnum.suspended, UserStatusEnum.removed],
[UserStatusEnum.suspended]: [UserStatusEnum.confirmed, UserStatusEnum.removed],
[UserStatusEnum.removed]: [],
};

Expand Down
Loading