diff --git a/.github/diagrams/.$exercise-selection.activity.drawio.bkp b/.github/diagrams/.$exercise-selection.activity.drawio.bkp
new file mode 100644
index 0000000..bae8fc9
--- /dev/null
+++ b/.github/diagrams/.$exercise-selection.activity.drawio.bkp
@@ -0,0 +1,104 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.github/diagrams/exercise-selection.activity.drawio b/.github/diagrams/exercise-selection.activity.drawio
new file mode 100644
index 0000000..bae8fc9
--- /dev/null
+++ b/.github/diagrams/exercise-selection.activity.drawio
@@ -0,0 +1,104 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index b8b5a90..ea1c999 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -24,4 +24,4 @@ jobs:
run: npm run install:all
- name: Build Project
- run: npm run build:all
+ run: npm run build
diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml
index e20772c..4d96853 100644
--- a/.github/workflows/package.yml
+++ b/.github/workflows/package.yml
@@ -41,7 +41,7 @@ jobs:
run: npm run install:all
- name: Build Project
- run: npm run build:all
+ run: npm run build
- name: Package VSIX
run: npm run package
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index b7770b1..a68a217 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -5,7 +5,7 @@
"tasks": [
{
"type": "npm",
- "script": "compile",
+ "script": "build",
"group": {
"kind": "build",
"isDefault": true
diff --git a/package-lock.json b/package-lock.json
index 491688e..34ca409 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7361,9 +7361,9 @@
"license": "MIT"
},
"node_modules/undici": {
- "version": "6.20.1",
- "resolved": "https://registry.npmjs.org/undici/-/undici-6.20.1.tgz",
- "integrity": "sha512-AjQF1QsmqfJys+LXfGTNum+qw4S88CojRInG/6t31W/1fk6G59s92bnAvGz5Cmur+kQv2SURXEvvudLmbrE8QA==",
+ "version": "6.21.1",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.1.tgz",
+ "integrity": "sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ==",
"license": "MIT",
"engines": {
"node": ">=18.17"
diff --git a/package.json b/package.json
index aaca52a..c948dbf 100644
--- a/package.json
+++ b/package.json
@@ -201,21 +201,26 @@
}
},
"scripts": {
- "compile": "webpack",
- "watch": "webpack --watch",
- "build": "webpack --mode production",
+ "install:all": "npm run install:webview && npm run install:extension",
+ "build": "npm run build:webview && npm run build:extension",
+ "watch": "npm run watch:webview && npm run watch:extension",
+
+ "install:extension": "npm install --no-scripts",
+ "build:extension": "webpack --mode production",
+ "compile:extension": "webpack",
+ "watch:extension": "webpack --watch",
+
+ "install:webview": "cd webview && npm install",
+ "build:webview": "cd webview && npm run build",
+ "watch:webview": "cd webview && npm run watch",
+
"test": "vscode-test-web --browserType=chromium --extensionDevelopmentPath=. --extensionTestsPath=dist/web/test/suite/index.js",
- "pretest": "npm run compile",
+ "pretest": "npm run compile:extension",
+ "lint": "eslint src --ext ts",
"package": "vsce package",
"vscode:prepublish": "npm run build",
- "lint": "eslint src --ext ts",
"run-in-browser": "vscode-test-web --browserType=chromium --extensionDevelopmentPath=. .",
- "open-in-browser": "vscode-test-web --extensionDevelopmentPath=. .",
- "start:webview": "cd webview && npm run start",
- "build:webview": "cd webview && npm run build",
- "install:webview": "cd webview && npm install",
- "install:all": "npm install && npm run install:webview",
- "build:all": "npm run build && npm run build:webview"
+ "open-in-browser": "vscode-test-web --extensionDevelopmentPath=. ."
},
"devDependencies": {
"@types/assert": "^1.5.10",
diff --git a/shared/models/exercise.model.ts b/shared/models/exercise.model.ts
index fc37ac8..13e86dc 100644
--- a/shared/models/exercise.model.ts
+++ b/shared/models/exercise.model.ts
@@ -1,5 +1,6 @@
import { Course } from "./course.model";
-import { StudentParticipation } from "./participation.model";
+import { getLatestResult, StudentParticipation } from "./participation.model";
+import { TestCase } from "./testcase.model";
export abstract class Exercise {
public id?: number;
@@ -23,6 +24,7 @@ export abstract class Exercise {
public exampleSolutionPublicationDate?: Date;
public studentParticipations?: StudentParticipation[];
+ public testCases?: TestCase[];
public course?: Course;
constructor(type: ExerciseType) {}
@@ -103,4 +105,9 @@ export function getProjectKey(course: Course | undefined, exercise: Exercise | u
}
return `${course.shortName!.toUpperCase()}${exercise.shortName!.toUpperCase()}`;
+}
+
+export function getScoreString(exercise: Exercise): string {
+ const score = getLatestResult(exercise.studentParticipations?.at(0))?.score;
+ return score ? `${score} %` : "No graded result";
}
\ No newline at end of file
diff --git a/shared/models/feedback.model.ts b/shared/models/feedback.model.ts
index a72fa04..531e024 100644
--- a/shared/models/feedback.model.ts
+++ b/shared/models/feedback.model.ts
@@ -19,6 +19,7 @@ export class Feedback {
public result?: Result;
public positive?: boolean;
public testCase?: TestCase;
+ public testCaseId?: number;
constructor() {}
}
diff --git a/shared/models/participation.model.ts b/shared/models/participation.model.ts
index 213bed0..f8adbde 100644
--- a/shared/models/participation.model.ts
+++ b/shared/models/participation.model.ts
@@ -27,3 +27,25 @@ export function getLatestResult(participation: StudentParticipation | undefined)
return latestResult.id! > currentResult.id! ? latestResult : currentResult;
})
}
+
+export function getProjectKeyFromRepoUrl(repoUrl: string): string {
+ // extract projectKey {protocol}://{username}@{host}:{port}/git/{PROJECT_KEY}/{project_key}-{username}.git
+ const parts = repoUrl.split("/");
+ if (parts.length < 5) {
+ throw new Error("Invalid artemis repository URL does not contain project key");
+ }
+
+ const projectKey = parts[4];
+ return projectKey;
+}
+
+export function addVcsTokenToUrl(url: string, username: string, vsctoken: string): string {
+ const credentials = `://${username}:${vsctoken}@`;
+ if (!url.includes("@")) {
+ // the url has the format https://vcs-server.com
+ return url.replace("://", credentials);
+ } else {
+ // the url has the format https://username@vcs-server.com -> replace ://username@
+ return url.replace(/:\/\/.*@/, credentials);
+ }
+}
diff --git a/shared/webview-commands.ts b/shared/webview-commands.ts
index 278555d..0eac5da 100644
--- a/shared/webview-commands.ts
+++ b/shared/webview-commands.ts
@@ -12,13 +12,10 @@ export enum CommandFromWebview {
}
export enum CommandFromExtension {
- SHOW_LOGIN = "showLogin",
- SHOW_COURSE_SELECTION = "showCourseSelection",
- SHOW_EXERCISE_SELECTION = "showExerciseSelection",
- SHOW_PROBLEM_STATEMENT = "showProblemStatement",
+ SEND_LOGIN_STATE = "showLogin",
+ SEND_COURSE_EXERCISE_REPOKEY = "sendCourseAndExercise",
SEND_COURSE_OPTIONS = "sendCourseOptions",
SEND_EXERCISE_OPTIONS = "sendExerciseOptions",
- SEND_COURSE_AND_EXERCISE = "sendCourseAndExercise",
SEND_UML = "sendUml",
EASTER_EGG = "easterEgg",
}
diff --git a/src/exercise/exercise.api.ts b/src/exercise/exercise.api.ts
index 9bbc7ad..e738bf6 100644
--- a/src/exercise/exercise.api.ts
+++ b/src/exercise/exercise.api.ts
@@ -1,6 +1,7 @@
import { settings } from "../shared/settings";
import { Course } from "@shared/models/course.model";
import { Exercise } from "@shared/models/exercise.model";
+import { StudentParticipation } from "@shared/models/participation.model";
export async function fetch_programming_exercises_by_courseId(
token: string,
@@ -43,11 +44,53 @@ export async function fetch_programming_exercises_by_courseId(
});
}
-export async function fetch_course_exercise_projectKey(
+export async function fetch_course_exercise_by_repo_name(
token: string,
- projectKey: string
+ repoName: string
): Promise<{ course: Course; exercise: Exercise }> {
- const url = `${settings.base_url}/api/programming-exercises/project-key/${projectKey}`;
+ const url = `${settings.base_url}/api/programming-exercise-participations/repo-name/${repoName}`;
+
+ return fetch(url, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${token}`,
+ },
+ })
+ .then(async (response) => {
+ if (!response.ok) {
+ const errorText = await response.text();
+
+ throw new Error(
+ `HTTP error! status: ${response.status} message: ${errorText}`
+ );
+ }
+
+ const data: { [key: string]: any } = await response.json();
+
+ const course = data.exercise.course as Course;
+ data.exercise.course = null;
+ let exercise = data.exercise as Exercise;
+ data.exercise = null;
+ exercise.studentParticipations = [data as StudentParticipation];
+
+
+ return { course: course, exercise: exercise };
+ })
+ .catch((error) => {
+ if (error instanceof TypeError) {
+ throw new Error(`Could not reach the server: ${error.message}`);
+ }
+
+ throw error;
+ });
+}
+
+export async function fetch_exercise_by_id(
+ token: string,
+ exerciseId: number
+): Promise<{ course: Course; exercise: Exercise }> {
+ const url = `${settings.base_url}/api/exercises/${exerciseId}`;
return fetch(url, {
method: "GET",
diff --git a/src/exercise/exercise.ts b/src/exercise/exercise.ts
index 316e40a..dc75f3c 100644
--- a/src/exercise/exercise.ts
+++ b/src/exercise/exercise.ts
@@ -2,20 +2,12 @@ import * as vscode from "vscode";
import { state } from "../shared/state";
import { AUTH_ID } from "../authentication/authentication_provider";
import { Course } from "@shared/models/course.model";
-import { Exercise } from "@shared/models/exercise.model";
-import {
- start_exercise,
-} from "../participation/participation.api";
-import { getLatestResult } from "@shared/models/participation.model";
+import { Exercise, getScoreString } from "@shared/models/exercise.model";
+import { start_exercise } from "../participation/participation.api";
import { NotAuthenticatedError } from "../authentication/not_authenticated.error";
-import { fetch_course_exercise_projectKey } from "./exercise.api";
+import { fetch_course_exercise_by_repo_name, fetch_exercise_by_id } from "./exercise.api";
import { cloneUserRepo } from "../participation/cloning.service";
-function _getScoreString(exercise: Exercise): string {
- const score = getLatestResult(exercise.studentParticipations?.at(0))?.score;
- return score ? `${score} %` : "No graded result";
-}
-
export async function build_exercise_options(course: Course): Promise {
const exercises = course.exercises;
if (!exercises) {
@@ -28,7 +20,7 @@ export async function build_exercise_options(course: Course): Promise
.map((exercise) => ({
kind: vscode.QuickPickItemKind.Default,
label: exercise.title!,
- description: _getScoreString(exercise),
+ description: getScoreString(exercise),
detail: "No due date",
exercise: exercise,
}));
@@ -38,7 +30,7 @@ export async function build_exercise_options(course: Course): Promise
.map((exercise) => ({
kind: vscode.QuickPickItemKind.Default,
label: exercise.title!,
- description: _getScoreString(exercise),
+ description: getScoreString(exercise),
detail: `Due on ${exercise.dueDate!.toLocaleString()}`,
exercise: exercise,
}))
@@ -49,7 +41,7 @@ export async function build_exercise_options(course: Course): Promise
.map((exercise) => ({
kind: vscode.QuickPickItemKind.Default,
label: exercise.title!,
- description: _getScoreString(exercise),
+ description: getScoreString(exercise),
detail: `Due on ${exercise.dueDate!.toLocaleString()}`,
exercise: exercise,
}))
@@ -112,8 +104,26 @@ export async function cloneCurrentExercise() {
await cloneUserRepo(participation.repositoryUri!, participation.participantIdentifier!);
}
-export async function get_course_exercise_by_projectKey(
- projectKey: string
+export async function get_problem_statement_details(exercise: Exercise) {
+ // check if exercise has an active participation
+ let course: Course;
+
+ const studentParticipation = exercise.studentParticipations?.at(0);
+ if (studentParticipation) {
+ // if so query the details by repoUrl of the latest participation
+ ({ course: course, exercise: exercise } = await get_course_exercise_by_repoUrl(
+ studentParticipation.repositoryUri!
+ ));
+ } else {
+ // if not query the details (mainly the problem statement) by the exercise id
+ ({ course, exercise } = await get_course_exercise_by_exercise_id(exercise.id!));
+ }
+
+ return { course: course, exercise: exercise };
+}
+
+export async function get_course_exercise_by_repoUrl(
+ repoUrl: string
): Promise<{ course: Course; exercise: Exercise }> {
const session = await vscode.authentication.getSession(AUTH_ID, [], {
createIfNone: false,
@@ -123,10 +133,26 @@ export async function get_course_exercise_by_projectKey(
throw new NotAuthenticatedError();
}
- const { course: course, exercise: exercise } = await fetch_course_exercise_projectKey(
+ const repoName = repoUrl.split("/").pop()!.replace(".git", "");
+
+ const { course: course, exercise: exercise } = await fetch_course_exercise_by_repo_name(
session.accessToken,
- projectKey
+ repoName
);
return { course: course, exercise: exercise };
}
+
+async function get_course_exercise_by_exercise_id(exerciseId: number) {
+ const session = await vscode.authentication.getSession(AUTH_ID, [], {
+ createIfNone: false,
+ });
+
+ if (!session) {
+ throw new NotAuthenticatedError();
+ }
+
+ const { course: course, exercise: exercise } = await fetch_exercise_by_id(session.accessToken, exerciseId);
+
+ return { course: course, exercise: exercise };
+}
diff --git a/src/extension.ts b/src/extension.ts
index f9a7eff..fe9f86d 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -32,7 +32,9 @@ export function activate(context: vscode.ExtensionContext) {
listenToEvents();
- vscode.commands.executeCommand("scorpio.workspace.detectRepo");
+ detectRepoCourseAndExercise().catch((e) => {
+ console.error(e);
+ });
}
function initAuthentication(context: vscode.ExtensionContext) {
@@ -168,8 +170,7 @@ function registerCommands(context: vscode.ExtensionContext, sidebar: SidebarProv
// command to detect repo in workspace
context.subscriptions.push(
vscode.commands.registerCommand("scorpio.workspace.detectRepo", async () => {
- detectRepoCourseAndExercise()
- .catch((e) => {
+ detectRepoCourseAndExercise().catch((e) => {
_errorMessage(e, LogLevel.ERROR, "Failed to detect repo");
});
})
@@ -195,7 +196,9 @@ function registerCommands(context: vscode.ExtensionContext, sidebar: SidebarProv
function listenToEvents() {
// listen to workspace changes to display problem statement
vscode.workspace.onDidChangeWorkspaceFolders((event) => {
- vscode.commands.executeCommand("scorpio.workspace.detectRepo");
+ detectRepoCourseAndExercise().catch((e) => {
+ console.error(e);
+ });
});
}
diff --git a/src/participation/cloning.service.ts b/src/participation/cloning.service.ts
index 5d8c41f..f1957db 100644
--- a/src/participation/cloning.service.ts
+++ b/src/participation/cloning.service.ts
@@ -7,7 +7,7 @@ import simpleGit from "simple-git";
import * as path from "path";
import { retrieveVcsAccessToken } from "../authentication/authentication_api";
import { getWorkspaceFolder, theiaEnv } from "../theia/theia";
-import { addVcsTokenToUrl } from "../utils/cloning.utils";
+import { addVcsTokenToUrl } from "@shared/models/participation.model";
export async function cloneUserRepo(repoUrl: string, username: string) {
// get folder to clone repo into
diff --git a/src/participation/participation.api.ts b/src/participation/participation.api.ts
index 082a381..1979853 100644
--- a/src/participation/participation.api.ts
+++ b/src/participation/participation.api.ts
@@ -36,78 +36,3 @@ export async function start_exercise(
throw error;
});
}
-
-export async function fetch_latest_participation(
- token: string,
- exerciseId: number
-): Promise {
- const url = `${settings.base_url}/api/exercises/${exerciseId}/participation`;
-
- return fetch(url, {
- method: "GET",
- headers: {
- "Content-Type": "application/json",
- Authorization: `Bearer ${token}`,
- },
- })
- .then(async (response) => {
- if (!response.ok) {
- const errorText = await response.text();
-
- throw new Error(
- `HTTP error! status: ${response.status} message: ${errorText}`
- );
- }
-
- const data = await response.json();
-
- return data as StudentParticipation;
- })
- .catch((error) => {
- if (error instanceof TypeError) {
- throw new Error(`Could not reach the server: ${error.message}`);
- }
-
- throw error;
- });
-
-
-}
-
-export async function fetch_feedback(
- token: string,
- participationId: number,
- resultId: number
-): Promise {
- const url = `${settings.base_url}/api/participations/${participationId}/results/${resultId}/details`;
-
- return fetch(url, {
- method: "GET",
- headers: {
- "Content-Type": "application/json",
- Authorization: `Bearer ${token}`,
- },
- })
- .then(async (response) => {
- if (!response.ok) {
- const errorText = await response.text();
-
- throw new Error(
- `HTTP error! status: ${response.status} message: ${errorText}`
- );
- }
-
- const data = await response.json();
-
- return data as Feedback[];
- })
- .catch((error) => {
- if (error instanceof TypeError) {
- throw new Error(`Could not reach the server: ${error.message}`);
- }
-
- throw error;
- });
-
-
-}
diff --git a/src/shared/repository.service.ts b/src/shared/repository.service.ts
index 81db1ad..f0c1dd1 100644
--- a/src/shared/repository.service.ts
+++ b/src/shared/repository.service.ts
@@ -7,8 +7,8 @@ import { Exercise, getProjectKey } from "@shared/models/exercise.model";
import { clear_repo_state, set_repo_state, state } from "./state";
import simpleGit, { RemoteWithRefs, SimpleGit } from "simple-git";
import { getLevel1SubfoldersOfWorkspace } from "../utils/filetree";
-import { get_course_exercise_by_projectKey } from "../exercise/exercise";
-import { getProjectKeyFromRepoUrl } from "../utils/cloning.utils";
+import { get_course_exercise_by_repoUrl } from "../exercise/exercise";
+import { getProjectKeyFromRepoUrl } from "@shared/models/participation.model";
var gitRepo: SimpleGit | undefined;
@@ -34,6 +34,8 @@ export async function submitCurrentWorkspace() {
await gitRepo.add(".");
await gitRepo.commit("Submit workspace from artemis plugin");
await gitRepo.push();
+
+ vscode.window.showInformationMessage("Workspace submitted successfully");
}
export async function detectRepoCourseAndExercise() {
@@ -53,21 +55,19 @@ export async function detectRepoCourseAndExercise() {
return;
}
- const projectKey = getProjectKeyFromRepoUrl(foundRepoAndRemote.remote.refs.fetch!);
- if(projectKey === getProjectKey(state.repoCourse, state.repoExercise)) {
+ const repoUrl = foundRepoAndRemote.remote.refs.fetch!;
+ if(getProjectKeyFromRepoUrl(repoUrl) === getProjectKey(state.repoCourse, state.repoExercise)) {
console.log("Repo already detected");
gitRepo = foundRepoAndRemote.repo;
return;
}
- const course_exercise: { course: Course; exercise: Exercise } = await get_course_exercise_by_projectKey(
- projectKey
+ const course_exercise: { course: Course; exercise: Exercise } = await get_course_exercise_by_repoUrl(
+ repoUrl
);
gitRepo = foundRepoAndRemote.repo;
set_repo_state(course_exercise.course, course_exercise.exercise);
-
- return projectKey;
}
async function getArtemisRepo(
diff --git a/src/sidebar/sidebarProvider.ts b/src/sidebar/sidebarProvider.ts
index bd0b8ea..a9dfb97 100644
--- a/src/sidebar/sidebarProvider.ts
+++ b/src/sidebar/sidebarProvider.ts
@@ -10,9 +10,10 @@ import {
fetch_programming_exercises_by_courseId,
} from "../exercise/exercise.api";
import { CommandFromExtension, CommandFromWebview } from "@shared/webview-commands";
-import { get_course_exercise_by_projectKey } from "../exercise/exercise";
+import { get_problem_statement_details } from "../exercise/exercise";
import { fetch_uml } from "../problemStatement/uml.api";
import { getProjectKey } from "@shared/models/exercise.model";
+import { text } from "stream/consumers";
export class SidebarProvider implements vscode.WebviewViewProvider {
_view?: vscode.WebviewView;
@@ -121,8 +122,8 @@ export class SidebarProvider implements vscode.WebviewViewProvider {
break;
}
case CommandFromWebview.GET_EXERCISE_DETAILS: {
- const { course: course, exercise: exercise } = await get_course_exercise_by_projectKey(
- state.displayedCourse!.shortName! + state.displayedExercise!.shortName!
+ const { course: course, exercise: exercise } = await get_problem_statement_details(
+ state.displayedExercise!
);
set_displayed_state(course, exercise);
break;
@@ -212,13 +213,15 @@ export class SidebarProvider implements vscode.WebviewViewProvider {
private login(session: vscode.AuthenticationSession) {
this._view?.webview.postMessage({
- command: CommandFromExtension.SHOW_COURSE_SELECTION,
+ command: CommandFromExtension.SEND_LOGIN_STATE,
+ text: true,
});
}
private logout() {
this._view?.webview.postMessage({
- command: CommandFromExtension.SHOW_LOGIN,
+ command: CommandFromExtension.SEND_LOGIN_STATE,
+ text: false,
});
}
@@ -230,34 +233,14 @@ export class SidebarProvider implements vscode.WebviewViewProvider {
const course = state.displayedCourse;
const exercise = state.displayedExercise;
- if (!course && !exercise) {
- this._view?.webview.postMessage({
- command: CommandFromExtension.SHOW_COURSE_SELECTION,
- });
- return;
- }
-
- if (course && !exercise) {
- this._view?.webview.postMessage({
- command: CommandFromExtension.SHOW_EXERCISE_SELECTION,
- text: JSON.stringify({
- course: course,
- }),
- });
- return;
- }
-
- if (course && exercise) {
- this._view?.webview.postMessage({
- command: CommandFromExtension.SHOW_PROBLEM_STATEMENT,
- text: JSON.stringify({
- course: course,
- exercise: exercise,
- repoKey: repoKey,
- }),
- });
- return;
- }
+ this._view?.webview.postMessage({
+ command: CommandFromExtension.SEND_COURSE_EXERCISE_REPOKEY,
+ text: JSON.stringify({
+ course: course,
+ exercise: exercise,
+ repoKey: repoKey,
+ }),
+ });
}
private easterEgg() {
diff --git a/src/utils/cloning.utils.ts b/src/utils/cloning.utils.ts
deleted file mode 100644
index 3b6e0e5..0000000
--- a/src/utils/cloning.utils.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-export function addVcsTokenToUrl(url: string, username: string, vsctoken: string): string {
- const credentials = `://${username}:${vsctoken}@`;
- if (!url.includes("@")) {
- // the url has the format https://vcs-server.com
- return url.replace("://", credentials);
- } else {
- // the url has the format https://username@vcs-server.com -> replace ://username@
- return url.replace(/:\/\/.*@/, credentials);
- }
-}
-
-export function getProjectKeyFromRepoUrl(repoUrl: string): string {
- // extract projectKey {protocol}://{username}@{host}:{port}/git/{PROJECT_KEY}/{project_key}-{username}.git
- const parts = repoUrl.split("/");
- if (parts.length < 5) {
- throw new Error("Invalid artemis repository URL does not contain project key");
- }
-
- const projectKey = parts[4];
- return projectKey;
- }
\ No newline at end of file
diff --git a/webview/package-lock.json b/webview/package-lock.json
index 0399f9d..8dd5c75 100644
--- a/webview/package-lock.json
+++ b/webview/package-lock.json
@@ -6248,9 +6248,9 @@
"license": "MIT"
},
"node_modules/katex": {
- "version": "0.16.18",
- "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.18.tgz",
- "integrity": "sha512-LRuk0rPdXrecAFwQucYjMiIs0JFefk6N1q/04mlw14aVIVgxq1FO0MA9RiIIGVaKOB5GIP5GH4aBBNraZERmaQ==",
+ "version": "0.16.21",
+ "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.21.tgz",
+ "integrity": "sha512-XvqR7FgOHtWupfMiigNzmh+MgUVmDGU2kXZm899ZkPfcuoPuFxyHmXsgATDpFZDAXCI8tvinaVcDo8PIIJSo4A==",
"funding": [
"https://opencollective.com/katex",
"https://github.com/sponsors/katex"
diff --git a/webview/package.json b/webview/package.json
index 560994a..ff6e889 100644
--- a/webview/package.json
+++ b/webview/package.json
@@ -4,7 +4,6 @@
"private": true,
"scripts": {
"ng": "ng",
- "start": "ng serve",
"build": "ng build --output-hashing=none",
"watch": "ng build --watch --configuration development",
"test": "ng test"
diff --git a/webview/src/app/exercise-detail/exercise-detail.view.html b/webview/src/app/exercise-detail/exercise-detail.view.html
index 3c995ad..10bbc3d 100644
--- a/webview/src/app/exercise-detail/exercise-detail.view.html
+++ b/webview/src/app/exercise-detail/exercise-detail.view.html
@@ -7,4 +7,4 @@ {{ exercise().title }}
}
}
-
+
diff --git a/webview/src/app/exercise-detail/exercise-detail.view.ts b/webview/src/app/exercise-detail/exercise-detail.view.ts
index 21ae0f4..879fa59 100644
--- a/webview/src/app/exercise-detail/exercise-detail.view.ts
+++ b/webview/src/app/exercise-detail/exercise-detail.view.ts
@@ -27,10 +27,6 @@ export class ExerciseDetailView implements OnInit {
repoKey = input.required();
protected repoKeyEqualsDisplayed = computed(() => this.repoKey() === this.exercise().projectKey);
- latestResult: Signal = computed(() =>
- getLatestResult(this.exercise().studentParticipations?.at(0))
- );
-
now = computed(() => new Date());
constructor() {}
diff --git a/webview/src/app/exercise-detail/problem-statement/problem-statement.component.ts b/webview/src/app/exercise-detail/problem-statement/problem-statement.component.ts
index b9ec6b2..4f196e0 100644
--- a/webview/src/app/exercise-detail/problem-statement/problem-statement.component.ts
+++ b/webview/src/app/exercise-detail/problem-statement/problem-statement.component.ts
@@ -24,8 +24,8 @@ import { escapeStringForUseInRegex } from "./regex.util";
import { ProgrammingExercisePlantUmlExtensionWrapper } from "./markdown-util/plant-uml.plugin";
import { merge, Subscription } from "rxjs";
import { ProgrammingExerciseInstructionService } from "./programming-exercise.service";
-import { Result } from "@shared/models/result.model";
import { ProgrammingExerciseTaskExtensionWrapper, taskRegex } from "./markdown-util/task.plugin";
+import { getLatestResult } from "@shared/models/participation.model";
const taskDivElement = (exerciseId: number, taskId: number) => `pe-${exerciseId}-task-${taskId}`;
@@ -41,9 +41,7 @@ export class ProblemStatementComponent implements OnDestroy {
// accept exercise as input
exercise = input.required();
- latestResult = input.required();
-
- feedbackList: Signal = computed(() => this.latestResult()?.feedbacks ?? []);
+ latestResult = computed(() => getLatestResult(this.exercise().studentParticipations?.at(0)));
problemStatement: Signal = computed(() => this.renderMarkdown(this.exercise().problemStatement));
@@ -118,7 +116,7 @@ export class ProblemStatementComponent implements OnDestroy {
completeString: testMatch![0],
taskName: testMatch![1],
testIds: testMatch![2]
- ? this.programmingExerciseInstructionService.convertTestListToIds(testMatch![2], undefined)
+ ? this.programmingExerciseInstructionService.convertTestListToIds(testMatch![2], this.exercise().testCases)
: [],
};
});
@@ -155,9 +153,9 @@ export class ProblemStatementComponent implements OnDestroy {
const componentRef = this.viewContainerRef.createComponent(TaskButton);
componentRef.setInput("task", task);
- const matchedFeedback = this.feedbackList().filter((feedback: Feedback) =>
- task.testIds.includes(feedback.testCase!.id!)
- );
+ const matchedFeedback = this.latestResult()?.feedbacks?.filter((feedback: Feedback) =>
+ !feedback.testCaseId || task.testIds.includes(feedback.testCaseId)
+ ) ?? [];
componentRef.setInput("feedbackList", matchedFeedback ?? []);
this.renderer.appendChild(taskHtmlContainer, componentRef.location.nativeElement);
diff --git a/webview/src/app/state.service.ts b/webview/src/app/state.service.ts
index 5a324be..0ada623 100644
--- a/webview/src/app/state.service.ts
+++ b/webview/src/app/state.service.ts
@@ -35,34 +35,36 @@ export class StateService {
window.addEventListener("message", (event) => {
const message = event.data; // The JSON data
switch (message.command) {
- case CommandFromExtension.SHOW_LOGIN:
+ case CommandFromExtension.SEND_LOGIN_STATE:
+ const loggedIn = message.text;
this.changeState({
- viewState: ViewState.LOGIN,
+ viewState: loggedIn ? ViewState.COURSE_SELECTION : ViewState.LOGIN,
course: undefined,
exercise: undefined,
repoKey: undefined,
});
break;
- case CommandFromExtension.SHOW_COURSE_SELECTION:
- this.changeState({
- viewState: ViewState.COURSE_SELECTION,
- course: undefined,
- exercise: undefined,
- repoKey: undefined,
- });
- break;
- case CommandFromExtension.SHOW_EXERCISE_SELECTION:
- const {course: courseData} = JSON.parse(message.text);
- this.changeState({
- viewState: ViewState.EXERCISE_SELECTION,
- course: courseData,
- exercise: undefined,
- repoKey: undefined,
- });
- break;
- case CommandFromExtension.SHOW_PROBLEM_STATEMENT:
+ case CommandFromExtension.SEND_COURSE_EXERCISE_REPOKEY:
const { course: course, exercise: exercise, repoKey: repoKey } = JSON.parse(message.text);
+ if (!course) {
+ this.changeState({
+ viewState: ViewState.COURSE_SELECTION,
+ course: undefined,
+ exercise: undefined,
+ repoKey: undefined,
+ });
+ break;
+ }
+ if (!exercise) {
+ this.changeState({
+ viewState: ViewState.EXERCISE_SELECTION,
+ course: course,
+ exercise: undefined,
+ repoKey: undefined,
+ });
+ break;
+ }
exercise.dueDate = exercise.dueDate ? new Date(exercise.dueDate) : exercise.dueDate;
this.changeState({
viewState: ViewState.PROBLEM_STATEMENT,