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,