diff --git a/.github/issue-labeler.yml b/.github/issue-labeler.yml index 34ca1e28321c..8fc424b1e785 100644 --- a/.github/issue-labeler.yml +++ b/.github/issue-labeler.yml @@ -111,7 +111,6 @@ programming: - build plan - code hint - \b(? -
LocalCIBuildJobQueueItem
id: String
name: String
buildAgentAddress: String
participationId: long
courseId: long
exerciseId: long
retryCount: int
priority: int
status: BuildJobResult
RepositoryInfo
repositoryName: String
repositoryType: RepositoryType
triggeredByPushTo: RepositoryType
assignmentRepositoryUri: String
testRepositoryUri: String
solutionRepositoryUri: String
auxiliaryRepositoryUris: String []
auxiliaryRepositoryCheckoutDirectories: String []
<<Enumeration>>
BuildJobResult
SUCCESSFUL
FAILED
CANCELLED
<<Enumeration>>
RepositoryType
TEMPLATE
SOLUTION
TESTS
AUXILIARY
USER
JobTimingInfo
submissionDate: ZonedDateTime
buildStartDate: ZonedDateTime
buildCompletionDate: ZonedDateTime
1
BuildConfig
buildScript: String
dockerImage: String
commitHash: String
commitHash: String
branch: String
programmingLanguage: ProgrammingLanguage
projectType: ProjectType
scaEnabled: boolean
sequentialTestRunsEnabled: boolean
testwiseCoverageEnabled: boolean
resultPaths: String []
1
1
\ No newline at end of file +
LocalCIBuildJobQueueItem
id: String
name: String
buildAgentAddress: String
participationId: long
courseId: long
exerciseId: long
retryCount: int
priority: int
status: BuildJobResult
RepositoryInfo
repositoryName: String
repositoryType: RepositoryType
triggeredByPushTo: RepositoryType
assignmentRepositoryUri: String
testRepositoryUri: String
solutionRepositoryUri: String
auxiliaryRepositoryUris: String []
auxiliaryRepositoryCheckoutDirectories: String []
<<Enumeration>>
BuildJobResult
SUCCESSFUL
FAILED
CANCELLED
<<Enumeration>>
RepositoryType
TEMPLATE
SOLUTION
TESTS
AUXILIARY
USER
JobTimingInfo
submissionDate: ZonedDateTime
buildStartDate: ZonedDateTime
buildCompletionDate: ZonedDateTime
1
BuildConfig
buildScript: String
dockerImage: String
commitHash: String
commitHash: String
branch: String
programmingLanguage: ProgrammingLanguage
projectType: ProjectType
scaEnabled: boolean
sequentialTestRunsEnabled: boolean
resultPaths: String []
1
1
diff --git a/docs/user/exercises/programming-exercise-features.inc b/docs/user/exercises/programming-exercise-features.inc index 18434aaed70f..1b43ab3cae7b 100644 --- a/docs/user/exercises/programming-exercise-features.inc +++ b/docs/user/exercises/programming-exercise-features.inc @@ -54,43 +54,43 @@ Instructors can still use those templates to generate programming exercises and In case a feature has different support for different continuous integration systems, the table shows the differences between Local CI **(L)** and Jenkins **(J)**. (Note that Gitlab CI is experimental and therefore has a limited set of features which are not mentioned here to keep the overview simpler.) - +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+----------------------------+------------------------+ - | Programming Language | Sequential Test Runs | Static Code Analysis | Plagiarism Check | Package Name | Project Type | Solution Repository Checkout | Testwise Coverage Analysis | Auxiliary repositories | - +======================+======================+======================+=====================+==============+==========================================+==============================+============================+========================+ - | Java | yes | yes | yes | yes | Gradle, Maven, J: `DejaGnu`_ | no | J: yes; L: no | L: yes, J: no | - +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+----------------------------+------------------------+ - | Python | L: yes; J: no | no | yes | no | n/a | no | no | L: yes, J: no | - +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+----------------------------+------------------------+ - | C | no | L: yes; J: no | yes | no | FACT, GCC | no | no | L: yes, J: no | - +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+----------------------------+------------------------+ - | C (FACT framework) | no | L: yes; J: no | yes | no | n/a | no | no | L: yes, J: no | - +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+----------------------------+------------------------+ - | Haskell | L: yes; J: no | no | no | no | n/a | L: yes; J: no | no | L: yes, J: no | - +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+----------------------------+------------------------+ - | Kotlin | yes | no | yes | yes | n/a | no | J: yes; L: no | L: yes, J: no | - +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+----------------------------+------------------------+ - | VHDL | no | no | no | no | n/a | no | no | L: yes, J: no | - +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+----------------------------+------------------------+ - | Assembler | no | no | no | no | n/a | no | no | L: yes, J: no | - +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+----------------------------+------------------------+ - | Swift | no | yes | yes | yes | Plain (Xcode: not supported) | no | no | L: yes, J: no | - +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+----------------------------+------------------------+ - | OCaml | no | no | no | no | n/a | yes | no | L: yes, J: no | - +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+----------------------------+------------------------+ - | Rust | no | no | yes | no | n/a | no | no | L: yes, J: no | - +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+----------------------------+------------------------+ - | JavaScript | no | no | yes | no | n/a | no | no | L: yes, J: no | - +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+----------------------------+------------------------+ - | R | no | no | yes | no | n/a | no | no | L: yes, J: no | - +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+----------------------------+------------------------+ - | C++ | no | no | yes | no | n/a | no | no | L: yes, J: no | - +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+----------------------------+------------------------+ - | TypeScript | no | no | yes | no | n/a | no | no | L: yes, J: no | - +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+----------------------------+------------------------+ - | C# | no | no | yes | no | n/a | no | no | L: yes, J: no | - +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+----------------------------+------------------------+ - | Go | no | no | yes | yes | n/a | no | no | L: yes, J: no | - +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+----------------------------+------------------------+ + +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+------------------------+ + | Programming Language | Sequential Test Runs | Static Code Analysis | Plagiarism Check | Package Name | Project Type | Solution Repository Checkout | Auxiliary repositories | + +======================+======================+======================+=====================+==============+==========================================+==============================+========================+ + | Java | yes | yes | yes | yes | Gradle, Maven, J: `DejaGnu`_ | no | L: yes, J: no | + +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+------------------------+ + | Python | L: yes; J: no | no | yes | no | n/a | no | L: yes, J: no | + +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+------------------------+ + | C | no | L: yes; J: no | yes | no | FACT, GCC | no | L: yes, J: no | + +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+------------------------+ + | C (FACT framework) | no | L: yes; J: no | yes | no | n/a | no | L: yes, J: no | + +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+------------------------+ + | Haskell | L: yes; J: no | no | no | no | n/a | L: yes; J: no | L: yes, J: no | + +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+------------------------+ + | Kotlin | yes | no | yes | yes | n/a | no | L: yes, J: no | + +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+------------------------+ + | VHDL | no | no | no | no | n/a | no | L: yes, J: no | + +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+------------------------+ + | Assembler | no | no | no | no | n/a | no | L: yes, J: no | + +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+------------------------+ + | Swift | no | yes | yes | yes | Plain (Xcode: not supported) | no | L: yes, J: no | + +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+------------------------+ + | OCaml | no | no | no | no | n/a | yes | L: yes, J: no | + +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+------------------------+ + | Rust | no | no | yes | no | n/a | no | L: yes, J: no | + +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+------------------------+ + | JavaScript | no | no | yes | no | n/a | no | L: yes, J: no | + +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+------------------------+ + | R | no | no | yes | no | n/a | no | L: yes, J: no | + +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+------------------------+ + | C++ | no | no | yes | no | n/a | no | L: yes, J: no | + +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+------------------------+ + | TypeScript | no | no | yes | no | n/a | no | L: yes, J: no | + +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+------------------------+ + | C# | no | no | yes | no | n/a | no | L: yes, J: no | + +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+------------------------+ + | Go | no | no | yes | yes | n/a | no | L: yes, J: no | + +----------------------+----------------------+----------------------+---------------------+--------------+------------------------------------------+------------------------------+------------------------+ - *Sequential Test Runs*: ``Artemis`` can generate a build plan which first executes structural and then behavioral tests. This feature can help students to better concentrate on the immediate challenge at hand. - *Static Code Analysis*: ``Artemis`` can generate a build plan which additionally executes static code analysis tools. @@ -98,8 +98,6 @@ Instructors can still use those templates to generate programming exercises and - *Plagiarism Checks*: ``Artemis`` is able to automatically calculate the similarity between student submissions. A side-by-side view of similar submissions is available to confirm the plagiarism suspicion. - *Package Name*: A package name has to be provided - *Solution Repository Checkout*: Instructors are able to compare a student submission against a sample solution in the solution repository - - *Testwise Coverage Analysis*: ``Artemis`` can generate a build plan which additionally executes a testwise coverage analysis. - ``Artemis`` aggregates the recorded data into different metrics. This feature allows instructors to check which code in the solution submission is how often executed by the test cases. .. note:: Only some ``templates`` for ``LocalCI`` support ``Sequential Test Runs`` at the moment. @@ -107,9 +105,6 @@ Instructors can still use those templates to generate programming exercises and .. note:: Static Code Analysis for ``C`` exercises is only supported for ``LocalCI`` at the moment. -.. note:: - Testwise Coverage Analysis is currently not supported, but will be again supported in future versions. - .. note:: Instructors are still able to extend the generated programming exercises with additional features that are not available in one specific template. diff --git a/docs/user/exercises/programming-exercise-setup.inc b/docs/user/exercises/programming-exercise-setup.inc index 563f65f48361..b95420105763 100644 --- a/docs/user/exercises/programming-exercise-setup.inc +++ b/docs/user/exercises/programming-exercise-setup.inc @@ -106,9 +106,6 @@ Generate programming exercise - **Sequential Test Runs:** Activate this option to first run structural and then behavior tests. This feature allows students to better concentrate on the immediate challenge at hand. Not supported together with static code analysis. Cannot be changed after the exercise creation. - - **Record Testwise Coverage:** Activate this option to record the testwise coverage for the solution repository. - This is necessary when working with Hestia to generate code-based hints. - This option is only available for Java/Kotlin-exercises with non-sequential test runs. - **Customize Build Plan** Activate this option if you want to customize the build plan of your exercise. This feature is available for all programming languages, and works with LocalCI and Jenkins, Artemis provides templates for the build plan configuration. The build plan can also be customized after the exercise creation. diff --git a/gradle.properties b/gradle.properties index 646b23dcf6fa..37430dbcce1e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -28,7 +28,7 @@ jplag_version=5.1.0 # NOTE: we cannot need to use the latest version 9.x or 10.x here as long as Stanford CoreNLP does not reference it lucene_version=8.11.4 slf4j_version=2.0.16 -sentry_version=7.19.1 +sentry_version=7.20.0 liquibase_version=4.30.0 docker_java_version=3.4.1 logback_version=1.5.15 @@ -41,7 +41,7 @@ mysql_version=9.1.0 # make sure both versions are compatible junit_version=5.11.3 junit_platform_version=1.11.4 -mockito_version=5.14.2 +mockito_version=5.15.2 testcontainer_version=1.20.4 # gradle plugin version diff --git a/gradle/liquibase.gradle b/gradle/liquibase.gradle index dd48fa3cb338..70ed9b74070c 100644 --- a/gradle/liquibase.gradle +++ b/gradle/liquibase.gradle @@ -19,12 +19,12 @@ ext.isWindows = OperatingSystem.current().isWindows() if (isWindows) { tasks.register("pathingLiquibaseJar", Jar) { - dependsOn configurations.liquibase + dependsOn configurations.liquibaseRuntime archiveAppendix = "pathingLiquibase" doFirst { manifest { - attributes "Class-Path": (sourceSets.main.runtimeClasspath + configurations.liquibase).collect { + attributes "Class-Path": (sourceSets.main.runtimeClasspath + configurations.liquibaseRuntime).collect { it.toURI().toURL().toString().replaceFirst(/file:\/+/, "/") }.join(" ") } @@ -46,7 +46,7 @@ def liquibaseCommand(command) { classpath tasks.named("pathingLiquibaseJar").get().outputs.files } else { classpath sourceSets.main.runtimeClasspath - classpath configurations.liquibase + classpath configurations.liquibaseRuntime } mainClass = "liquibase.integration.commandline.Main" diff --git a/gradle/test.gradle b/gradle/test.gradle index d09c3a9c61c4..5005a226659b 100644 --- a/gradle/test.gradle +++ b/gradle/test.gradle @@ -57,7 +57,8 @@ test { useJUnitPlatform() exclude "**/*IT*", "**/*IntTest*" } - + testClassesDirs = testing.suites.test.sources.output.classesDirs + classpath = testing.suites.test.sources.runtimeClasspath testLogging { events "FAILED", "SKIPPED" } @@ -107,13 +108,13 @@ jacocoTestCoverageVerification { counter = "INSTRUCTION" value = "COVEREDRATIO" // TODO: in the future the following value should become higher than 0.92 - minimum = 0.895 + minimum = 0.891 } limit { counter = "CLASS" value = "MISSEDCOUNT" // TODO: in the future the following value should become less than 10 - maximum = 56 + maximum = 57 } } } diff --git a/jest.config.js b/jest.config.js index a5ce2254e1a5..ab2c159af355 100644 --- a/jest.config.js +++ b/jest.config.js @@ -105,10 +105,10 @@ module.exports = { coverageThreshold: { global: { // TODO: in the future, the following values should increase to at least 90% - statements: 87.71, - branches: 73.85, - functions: 82.28, - lines: 87.77, + statements: 87.81, + branches: 73.97, + functions: 82.50, + lines: 87.86, }, }, coverageReporters: ['clover', 'json', 'lcov', 'text-summary'], diff --git a/package-lock.json b/package-lock.json index df82b26a8c98..a543cb1fb222 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,23 +1,23 @@ { "name": "artemis", - "version": "7.8.1", + "version": "7.8.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "artemis", - "version": "7.8.1", + "version": "7.8.3", "hasInstallScript": true, "license": "MIT", "dependencies": { "@angular/animations": "18.2.13", - "@angular/cdk": "18.2.13", + "@angular/cdk": "18.2.14", "@angular/common": "18.2.13", "@angular/compiler": "18.2.13", "@angular/core": "18.2.13", "@angular/forms": "18.2.13", "@angular/localize": "18.2.13", - "@angular/material": "18.2.13", + "@angular/material": "18.2.14", "@angular/platform-browser": "18.2.13", "@angular/platform-browser-dynamic": "18.2.13", "@angular/router": "18.2.13", @@ -36,7 +36,7 @@ "@sentry/angular": "8.47.0", "@siemens/ngx-datatable": "22.4.1", "@swimlane/ngx-charts": "21.1.2", - "@swimlane/ngx-graph": "8.4.0", + "@swimlane/ngx-graph": "9.0.1", "@vscode/codicons": "0.0.36", "@vscode/markdown-it-katex": "1.1.1", "bootstrap": "5.3.3", @@ -46,7 +46,7 @@ "dayjs": "1.11.13", "diff-match-patch-typescript": "1.1.0", "dompurify": "3.2.3", - "emoji-js": "3.8.0", + "emoji-js": "3.8.1", "export-to-csv": "1.4.0", "fast-json-patch": "3.1.1", "franc-min": "6.2.0", @@ -65,7 +65,7 @@ "ngx-webstorage": "18.0.0", "papaparse": "5.4.1", "pdf-lib": "1.17.1", - "pdfjs-dist": "4.9.155", + "pdfjs-dist": "4.10.38", "rxjs": "7.8.1", "simple-statistics": "7.8.7", "smoothscroll-polyfill": "0.4.4", @@ -74,20 +74,20 @@ "ts-cacheable": "1.0.10", "tslib": "2.8.1", "turndown": "7.2.0", - "uuid": "11.0.3", + "uuid": "11.0.4", "webstomp-client": "1.2.6", "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz", "zone.js": "0.14.10" }, "devDependencies": { - "@analogjs/vite-plugin-angular": "1.10.3", + "@analogjs/vite-plugin-angular": "1.11.0", "@angular-builders/jest": "18.0.0", "@angular-devkit/build-angular": "18.2.12", - "@angular-eslint/builder": "18.4.1", - "@angular-eslint/eslint-plugin": "18.4.1", - "@angular-eslint/eslint-plugin-template": "18.4.1", - "@angular-eslint/schematics": "18.4.1", - "@angular-eslint/template-parser": "18.4.1", + "@angular-eslint/builder": "18.4.3", + "@angular-eslint/eslint-plugin": "18.4.3", + "@angular-eslint/eslint-plugin-template": "18.4.3", + "@angular-eslint/schematics": "18.4.3", + "@angular-eslint/template-parser": "18.4.3", "@angular/cli": "18.2.12", "@angular/compiler-cli": "18.2.13", "@angular/language-service": "18.2.13", @@ -100,21 +100,21 @@ "@types/jest": "29.5.14", "@types/lodash-es": "4.17.12", "@types/markdown-it": "14.1.2", - "@types/node": "22.10.2", + "@types/node": "22.10.5", "@types/papaparse": "5.3.15", "@types/smoothscroll-polyfill": "0.3.4", "@types/sockjs-client": "1.5.4", "@types/turndown": "5.0.5", "@types/uuid": "10.0.0", - "@typescript-eslint/eslint-plugin": "8.18.1", - "@typescript-eslint/parser": "8.18.1", + "@typescript-eslint/eslint-plugin": "8.19.0", + "@typescript-eslint/parser": "8.19.0", "eslint": "9.17.0", "eslint-config-prettier": "9.1.0", "eslint-plugin-deprecation": "3.0.0", "eslint-plugin-jest": "28.10.0", "eslint-plugin-jest-extended": "2.4.0", "eslint-plugin-prettier": "5.2.1", - "folder-hash": "4.0.4", + "folder-hash": "4.1.0", "husky": "9.1.7", "jest": "29.7.0", "jest-canvas-mock": "2.5.2", @@ -123,14 +123,16 @@ "jest-fail-on-console": "3.3.1", "jest-junit": "16.0.0", "jest-preset-angular": "14.4.2", - "lint-staged": "15.2.11", + "lint-staged": "15.3.0", "ng-mocks": "14.13.1", "ngxtension": "4.2.0", + "patch-package": "8.0.0", "prettier": "3.4.2", "rimraf": "6.0.1", - "sass": "1.83.0", + "sass": "1.83.1", "ts-jest": "29.2.5", "typescript": "5.5.4", + "typescript-eslint": "8.19.0", "vite-tsconfig-paths": "5.1.4", "vitest": "2.1.8", "weak-napi": "2.0.2" @@ -153,9 +155,9 @@ } }, "node_modules/@analogjs/vite-plugin-angular": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/@analogjs/vite-plugin-angular/-/vite-plugin-angular-1.10.3.tgz", - "integrity": "sha512-3EWappJ5K6YopJpq2QRVim8qZgaTQJD0RB4G/DXo+Fg0s27BjDORiaeixqLHIwhUzzZ5FR2d1S7dgIi9zOg4sA==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@analogjs/vite-plugin-angular/-/vite-plugin-angular-1.11.0.tgz", + "integrity": "sha512-18HSwOAVFQjwwRQPq9+duOoubuiut0INo55h0gV3v2TWS+tAP2wXP8SPyAG99P3ySNQB7zMUYE8mVsqLM+8bDA==", "dev": true, "license": "MIT", "dependencies": { @@ -179,53 +181,6 @@ } } }, - "node_modules/@analogjs/vite-plugin-angular/node_modules/@ts-morph/common": { - "version": "0.22.0", - "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.22.0.tgz", - "integrity": "sha512-HqNBuV/oIlMKdkLshXd1zKBqNQCsuPEsgQOkfFQ/eUKjRlwndXW1AjN9LVkBEIukm00gGXSRmfkl0Wv5VXLnlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-glob": "^3.3.2", - "minimatch": "^9.0.3", - "mkdirp": "^3.0.1", - "path-browserify": "^1.0.1" - } - }, - "node_modules/@analogjs/vite-plugin-angular/node_modules/code-block-writer": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-12.0.0.tgz", - "integrity": "sha512-q4dMFMlXtKR3XNBHyMHt/3pwYNA69EDk00lloMOaaUMKPUXBw6lpXtbu3MMVG6/uOihGnRDOlkyqsONEUj60+w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@analogjs/vite-plugin-angular/node_modules/mkdirp": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", - "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", - "dev": true, - "license": "MIT", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@analogjs/vite-plugin-angular/node_modules/ts-morph": { - "version": "21.0.1", - "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-21.0.1.tgz", - "integrity": "sha512-dbDtVdEAncKctzrVZ+Nr7kHpHkv+0JDJb2MjjpBaj8bFeCkePU9rHfMklmhuLFnpeq/EJZk2IhStY6NzqgjOkg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@ts-morph/common": "~0.22.0", - "code-block-writer": "^12.0.0" - } - }, "node_modules/@angular-builders/common": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@angular-builders/common/-/common-2.0.0.tgz", @@ -265,6 +220,19 @@ "jest": ">=29" } }, + "node_modules/@angular-builders/jest/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/@angular-builders/jest/node_modules/jest-preset-angular": { "version": "14.1.0", "resolved": "https://registry.npmjs.org/jest-preset-angular/-/jest-preset-angular-14.1.0.tgz", @@ -294,6 +262,28 @@ "typescript": ">=4.8" } }, + "node_modules/@angular-builders/jest/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@angular-builders/jest/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, "node_modules/@angular-devkit/architect": { "version": "0.1802.12", "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.12.tgz", @@ -539,32 +529,36 @@ } }, "node_modules/@angular-eslint/builder": { - "version": "18.4.1", - "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-18.4.1.tgz", - "integrity": "sha512-Ofkwd9Rg52K+AgvnV1RXYXVBGJvl5jD7+4dqwoprqXG7YKNTdHy5vqNZ5XDSMb26qjoZF7JC+IKruKFaON/ZaA==", + "version": "18.4.3", + "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-18.4.3.tgz", + "integrity": "sha512-NzmrXlr7GFE+cjwipY/CxBscZXNqnuK0us1mO6Z2T6MeH6m+rRcdlY/rZyKoRniyNNvuzl6vpEsfMIMmnfebrA==", "dev": true, "license": "MIT", + "dependencies": { + "@angular-devkit/architect": ">= 0.1800.0 < 0.1900.0", + "@angular-devkit/core": ">= 18.0.0 < 19.0.0" + }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": "*" } }, "node_modules/@angular-eslint/bundled-angular-compiler": { - "version": "18.4.1", - "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-18.4.1.tgz", - "integrity": "sha512-gCQC0mgBO1bwHDXL9CUgHW+Rf1XGZCLAopoXnggwxGkBCx+oww507t+jrSOxdh+4OTKU4ZfmbtWd7Y8AeXns8w==", + "version": "18.4.3", + "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-18.4.3.tgz", + "integrity": "sha512-zdrA8mR98X+U4YgHzUKmivRU+PxzwOL/j8G7eTOvBuq8GPzsP+hvak+tyxlgeGm9HsvpFj9ERHLtJ0xDUPs8fg==", "dev": true, "license": "MIT" }, "node_modules/@angular-eslint/eslint-plugin": { - "version": "18.4.1", - "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-18.4.1.tgz", - "integrity": "sha512-FoHwj+AFo8ONKb8wEK5qpo6uefuyklZlDqErJxeC3fpNIJzDe8PWBcJsuZt7Wwm/HeggWgt0Au6h+3IEa0V3BQ==", + "version": "18.4.3", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-18.4.3.tgz", + "integrity": "sha512-AyJbupiwTBR81P6T59v+aULEnPpZBCBxL2S5QFWfAhNCwWhcof4GihvdK2Z87yhvzDGeAzUFSWl/beJfeFa+PA==", "dev": true, "license": "MIT", "dependencies": { - "@angular-eslint/bundled-angular-compiler": "18.4.1", - "@angular-eslint/utils": "18.4.1" + "@angular-eslint/bundled-angular-compiler": "18.4.3", + "@angular-eslint/utils": "18.4.3" }, "peerDependencies": { "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", @@ -573,14 +567,14 @@ } }, "node_modules/@angular-eslint/eslint-plugin-template": { - "version": "18.4.1", - "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-18.4.1.tgz", - "integrity": "sha512-sofnKpi6wOZ6avVfYYqB7sCgGgWF2HgCZfW+IAp1MtVD2FBa1zTSbbfIZ1I8Akpd22UXa4LKJd0TLwm5XHHkiQ==", + "version": "18.4.3", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-18.4.3.tgz", + "integrity": "sha512-ijGlX2N01ayMXTpeQivOA31AszO8OEbu9ZQUCxnu9AyMMhxyi2q50bujRChAvN9YXQfdQtbxuajxV6+aiWb5BQ==", "dev": true, "license": "MIT", "dependencies": { - "@angular-eslint/bundled-angular-compiler": "18.4.1", - "@angular-eslint/utils": "18.4.1", + "@angular-eslint/bundled-angular-compiler": "18.4.3", + "@angular-eslint/utils": "18.4.3", "aria-query": "5.3.2", "axobject-query": "4.1.0" }, @@ -592,31 +586,29 @@ } }, "node_modules/@angular-eslint/schematics": { - "version": "18.4.1", - "resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-18.4.1.tgz", - "integrity": "sha512-1+gGodwh+UevtEx9mzZbzP1uY/9NAGEbsn8jisG1TEPDby2wKScQj6U6JwGxoW/Dd/4SIeSdilruZPALkqha7g==", + "version": "18.4.3", + "resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-18.4.3.tgz", + "integrity": "sha512-D5maKn5e6n58+8n7jLFLD4g+RGPOPeDSsvPc1sqial5tEKLxAJQJS9WZ28oef3bhkob6C60D+1H0mMmEEVvyVA==", "dev": true, "license": "MIT", "dependencies": { - "@angular-eslint/eslint-plugin": "18.4.1", - "@angular-eslint/eslint-plugin-template": "18.4.1", + "@angular-devkit/core": ">= 18.0.0 < 19.0.0", + "@angular-devkit/schematics": ">= 18.0.0 < 19.0.0", + "@angular-eslint/eslint-plugin": "18.4.3", + "@angular-eslint/eslint-plugin-template": "18.4.3", "ignore": "6.0.2", "semver": "7.6.3", "strip-json-comments": "3.1.1" - }, - "peerDependencies": { - "@angular-devkit/core": ">= 18.0.0 < 19.0.0", - "@angular-devkit/schematics": ">= 18.0.0 < 19.0.0" } }, "node_modules/@angular-eslint/template-parser": { - "version": "18.4.1", - "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-18.4.1.tgz", - "integrity": "sha512-LsStXVyso/89gQU5eiJebB/b1j+wrRtTLjk+ODVUTa7NGCCT7B7xI6ToTchkBEpSTHLT9pEQXHsHer3FymsQRQ==", + "version": "18.4.3", + "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-18.4.3.tgz", + "integrity": "sha512-JZMPtEB8yNip3kg4WDEWQyObSo2Hwf+opq2ElYuwe85GQkGhfJSJ2CQYo4FSwd+c5MUQAqESNRg9QqGYauDsiw==", "dev": true, "license": "MIT", "dependencies": { - "@angular-eslint/bundled-angular-compiler": "18.4.1", + "@angular-eslint/bundled-angular-compiler": "18.4.3", "eslint-scope": "^8.0.2" }, "peerDependencies": { @@ -625,13 +617,13 @@ } }, "node_modules/@angular-eslint/utils": { - "version": "18.4.1", - "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-18.4.1.tgz", - "integrity": "sha512-F5UGE1J/CRmTbl8vjexQRwRglNqnJwdXCUejaG+qlGssSHoWcRB+ubbR/na3PdnzeJdBE6DkLYElXnOQZ6YKfg==", + "version": "18.4.3", + "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-18.4.3.tgz", + "integrity": "sha512-w0bJ9+ELAEiPBSTPPm9bvDngfu1d8JbzUhvs2vU+z7sIz/HMwUZT5S4naypj2kNN0gZYGYrW0lt+HIbW87zTAQ==", "dev": true, "license": "MIT", "dependencies": { - "@angular-eslint/bundled-angular-compiler": "18.4.1" + "@angular-eslint/bundled-angular-compiler": "18.4.3" }, "peerDependencies": { "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", @@ -749,9 +741,9 @@ } }, "node_modules/@angular/cdk": { - "version": "18.2.13", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-18.2.13.tgz", - "integrity": "sha512-yBKoqcOwmwXnc5phFMEEMO130/Bz9beQLJrKzIS87f6TXaGCeBs4xrPHq2i7Xx/2TqvMiOD9ucjmlVbtGvNG3w==", + "version": "18.2.14", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-18.2.14.tgz", + "integrity": "sha512-vDyOh1lwjfVk9OqoroZAP8pf3xxKUvyl+TVR8nJxL4c5fOfUFkD7l94HaanqKSRwJcI2xiztuu92IVoHn8T33Q==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -864,9 +856,9 @@ } }, "node_modules/@angular/compiler-cli/node_modules/chokidar": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", - "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "license": "MIT", "dependencies": { "readdirp": "^4.0.1" @@ -960,16 +952,16 @@ } }, "node_modules/@angular/material": { - "version": "18.2.13", - "resolved": "https://registry.npmjs.org/@angular/material/-/material-18.2.13.tgz", - "integrity": "sha512-Gxyyo6G+IXJwgf6zDTjPfFJ2PnjC2YXWKGkKKG2oR0jfiYiovDvNR4oXxhsztTwkaxLwck/gscoVTSQXMkU5fg==", + "version": "18.2.14", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-18.2.14.tgz", + "integrity": "sha512-28pxzJP49Mymt664WnCtPkKeg7kXUsQKTKGf/Kl95rNTEdTJLbnlcc8wV0rT0yQNR7kXgpfBnG7h0ETLv/iu5Q==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" }, "peerDependencies": { "@angular/animations": "^18.0.0 || ^19.0.0", - "@angular/cdk": "18.2.13", + "@angular/cdk": "18.2.14", "@angular/common": "^18.0.0 || ^19.0.0", "@angular/core": "^18.0.0 || ^19.0.0", "@angular/forms": "^18.0.0 || ^19.0.0", @@ -1069,9 +1061,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.2.tgz", - "integrity": "sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.3.tgz", + "integrity": "sha512-nHIxvKPniQXpmQLb0vhY3VaFb3S0YrTAwpOWJZh1wn3oJPjJk9Asva204PsBdmAE8vpzfHudT8DB0scYvy9q0g==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -1140,20 +1132,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.25.9.tgz", - "integrity": "sha512-C47lC7LIDCnz0h4vai/tpNOI95tCd5ZT3iBt/DBH5lXKHZsyNQv18yf1wIIg2ntiQNgmAvA+DgZ82iW8Qdym8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-compilation-targets": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz", @@ -1206,14 +1184,14 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.9.tgz", - "integrity": "sha512-ORPNZ3h6ZRkOyAa/SaHU+XsLZr0UQzRwuDQ0cczIA17nAzZ+85G5cVkOJIj7QavLZGSe8QXUmNFxSZzjcZF9bw==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.26.3.tgz", + "integrity": "sha512-G7ZRb40uUgdKOQqPLjfD12ZmGA54PzqDFUv2BKImnC9QIfGhIHKvVML0oN8IUiDq4iRqpq74ABpvOaerfWdong==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.25.9", - "regexpu-core": "^6.1.1", + "regexpu-core": "^6.2.0", "semver": "^6.3.1" }, "engines": { @@ -1368,20 +1346,6 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-simple-access": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.25.9.tgz", - "integrity": "sha512-c6WHXuiaRsJTyHYLJV75t9IqsmTbItYfdj99PnzYGQZkYKvan5/2jKJ7gu31J3/BJ/A18grImSPModuyG/Eo0Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz", @@ -1465,12 +1429,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz", - "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.3.tgz", + "integrity": "sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==", "license": "MIT", "dependencies": { - "@babel/types": "^7.26.0" + "@babel/types": "^7.26.3" }, "bin": { "parser": "bin/babel-parser.js" @@ -2126,13 +2090,12 @@ } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.25.9.tgz", - "integrity": "sha512-KRhdhlVk2nObA5AYa7QMgTMTVJdfHprfpAk4DjZVtllqRg9qarilstTKEhpVjyt+Npi8ThRyiV8176Am3CodPA==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.26.3.tgz", + "integrity": "sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { @@ -2275,15 +2238,14 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.25.9.tgz", - "integrity": "sha512-dwh2Ol1jWwL2MgkCzUSOvfmKElqQcuswAZypBSUsScMXvgdT8Ekq5YA6TtqpTVWH+4903NmboMuH1o9i8Rxlyg==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz", + "integrity": "sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-simple-access": "^7.25.9" + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2883,16 +2845,16 @@ } }, "node_modules/@babel/traverse": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz", - "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==", + "version": "7.26.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.4.tgz", + "integrity": "sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.25.9", - "@babel/generator": "^7.25.9", - "@babel/parser": "^7.25.9", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.3", + "@babel/parser": "^7.26.3", "@babel/template": "^7.25.9", - "@babel/types": "^7.25.9", + "@babel/types": "^7.26.3", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -2901,13 +2863,13 @@ } }, "node_modules/@babel/traverse/node_modules/@babel/generator": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.2.tgz", - "integrity": "sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.3.tgz", + "integrity": "sha512-6FF/urZvD0sTeO7k6/B15pMLC4CHUv1426lzr3N01aHJTl046uCAh9LXW/fzeXXjPNCJ6iABW5XaWOsIZB93aQ==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.26.2", - "@babel/types": "^7.26.0", + "@babel/parser": "^7.26.3", + "@babel/types": "^7.26.3", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" @@ -2917,9 +2879,9 @@ } }, "node_modules/@babel/traverse/node_modules/jsesc": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", - "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "license": "MIT", "bin": { "jsesc": "bin/jsesc" @@ -2929,9 +2891,9 @@ } }, "node_modules/@babel/types": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", - "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.3.tgz", + "integrity": "sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.25.9", @@ -3504,13 +3466,13 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.0.tgz", - "integrity": "sha512-zdHg2FPIFNKPdcHWtiNT+jEFCHYVplAXRDlQDyqy0zGx/q2parwh7brGJSiTxRk/TSMkbM//zt/f5CHgyTyaSQ==", + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.1.tgz", + "integrity": "sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.4", + "@eslint/object-schema": "^2.1.5", "debug": "^4.3.1", "minimatch": "^3.1.2" }, @@ -3543,11 +3505,14 @@ } }, "node_modules/@eslint/core": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.9.0.tgz", - "integrity": "sha512-7ATR9F0e4W85D/0w7cU0SNj7qkAexMG+bAHEZOjo9akvGuhHE2m7umzWzfnpa0XAg5Kxc1BWmtPMV67jJ+9VUg==", + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.9.1.tgz", + "integrity": "sha512-GuUdqkyyzQI5RMIWkHhvTWLCyLo1jNK3vzkSyaExH5kHPDHcuL2VOpHjmMY+y3+NC69qAKToBqldTBgYeLSr9Q==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } @@ -3658,9 +3623,9 @@ } }, "node_modules/@eslint/object-schema": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", - "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.5.tgz", + "integrity": "sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -3668,9 +3633,9 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.3.tgz", - "integrity": "sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==", + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.4.tgz", + "integrity": "sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -3921,9 +3886,9 @@ } }, "node_modules/@inquirer/figures": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.8.tgz", - "integrity": "sha512-tKd+jsmhq21AP1LhexC0pPwsCxEhGgAkg28byjJAd+xhmIs8LUX8JbUc3vBf3PhLxWiB5EvyBE5X7JSPAqMAqg==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.9.tgz", + "integrity": "sha512-BXvGj0ehzrngHTPTDqUoDT3NXL8U0RxUk2zJm2A66RhCEIWdtU1v6GuUqNAgArW4PQ9CinqIWyHdQgdwOj06zQ==", "dev": true, "license": "MIT", "engines": { @@ -4365,6 +4330,41 @@ } } }, + "node_modules/@jest/core/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/core/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, "node_modules/@jest/core/node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -4639,9 +4639,9 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", "license": "MIT", "dependencies": { "@jridgewell/set-array": "^1.2.1", @@ -4715,9 +4715,9 @@ } }, "node_modules/@jsonjoy.com/json-pack": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.1.0.tgz", - "integrity": "sha512-zlQONA+msXPPwHWZMKFVS78ewFczIll5lXiVPwFPCZUsrOKdxc2AvxU1HoNBmMRhqDZUR9HkC3UOm+6pME6Xsg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.1.1.tgz", + "integrity": "sha512-osjeBqMJ2lb/j/M8NCPjs1ylqWIcTRTycIhVB5pt6LgzgeRSb0YRZ7j9RfA8wIUrsr/medIuhVyonXRZWLyfdw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -5537,9 +5537,9 @@ } }, "node_modules/@nx/devkit": { - "version": "20.1.4", - "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-20.1.4.tgz", - "integrity": "sha512-Opz7eRPmpt3e4SGkbwZbE9Bg3MhKeivh1QTNCj4tQVAB4gucz0lW/F3mdtRDFdj6gUbqIc5rRrbO/DGlNaEzYw==", + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-20.3.0.tgz", + "integrity": "sha512-u9oRd2F33DLNWPbzpYGW7xuMEYUAOwO9DLP9nGYpxbZXy6Z4AdoKeqhN+KBTyg8+DyQGuKUSEXcWriDyLLgcHw==", "dev": true, "license": "MIT", "dependencies": { @@ -5593,9 +5593,9 @@ } }, "node_modules/@nx/nx-darwin-arm64": { - "version": "20.1.4", - "resolved": "https://registry.npmjs.org/@nx/nx-darwin-arm64/-/nx-darwin-arm64-20.1.4.tgz", - "integrity": "sha512-afyDOZbIyHi6BgKk+Bb4RI1t8dZ6/oIbOY89z4mBPNNevZkbGqUfMwO2vjKnaOoThcjT93SEMJfCLGL8i857ww==", + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/@nx/nx-darwin-arm64/-/nx-darwin-arm64-20.3.0.tgz", + "integrity": "sha512-9PqSe1Sh7qNqA4GL0cZH0t3S0EZzb2Xn14XY9au7yf0+eoxyag1oETjjULrxLeUmSoXW2hDxzNtoqKFE9zF07Q==", "cpu": [ "arm64" ], @@ -5610,9 +5610,9 @@ } }, "node_modules/@nx/nx-darwin-x64": { - "version": "20.1.4", - "resolved": "https://registry.npmjs.org/@nx/nx-darwin-x64/-/nx-darwin-x64-20.1.4.tgz", - "integrity": "sha512-aiYklAt95aX0EinepJRryMna8K53G52ngYOFuac1G8iLlguinJvg/YgSKCf7GOAzec8b7Hm7KauPjSJE/P3/iw==", + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/@nx/nx-darwin-x64/-/nx-darwin-x64-20.3.0.tgz", + "integrity": "sha512-gsGGhJVvi5QZVVTZie5sNMo1zOAU+A2edm6DGegObdFRLV41Ju/Yrm/gTaSp4yUtywd3UU4S/30C/nI2c55adA==", "cpu": [ "x64" ], @@ -5627,9 +5627,9 @@ } }, "node_modules/@nx/nx-freebsd-x64": { - "version": "20.1.4", - "resolved": "https://registry.npmjs.org/@nx/nx-freebsd-x64/-/nx-freebsd-x64-20.1.4.tgz", - "integrity": "sha512-WUh4bsLK+e7wuN3lE3ZQUj+xQKdWU4P4RymutfLQQnPYiilCMtFwITcvDmazmOHFWI2vPhzSyYJRbOu+YMIR3A==", + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/@nx/nx-freebsd-x64/-/nx-freebsd-x64-20.3.0.tgz", + "integrity": "sha512-DiymYZBBu0upbiskdfn9KRyoXdyvKohezJiV3j4VkeRE8KR2p04NgwRQviDFbeD1cjWrDy9wk8y+G5PabLlqAA==", "cpu": [ "x64" ], @@ -5644,9 +5644,9 @@ } }, "node_modules/@nx/nx-linux-arm-gnueabihf": { - "version": "20.1.4", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-20.1.4.tgz", - "integrity": "sha512-9vPMw5s89v3od7aw3enTWjdMSCAmQ0tIA89Uz7xbbjB2kX2mAdihSzAKd9woi/cj+ROnY+ynNXzU9UjqhfxdBg==", + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-20.3.0.tgz", + "integrity": "sha512-Aksx66e8jmt/4rGJ/5z34SWXbPcYr9Ht52UonEeuCdQdoEvAOs7yBUbllYOjIcUsfZikEyZgvqfiQslsggSJdQ==", "cpu": [ "arm" ], @@ -5661,9 +5661,9 @@ } }, "node_modules/@nx/nx-linux-arm64-gnu": { - "version": "20.1.4", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-20.1.4.tgz", - "integrity": "sha512-JUE4l8utr9KmQSG9tO2Qw5R5i/bZ16s1+J5xnEar7UfcSOfOLqxGHS7HCBUZcfr46dmtv6KjIC83uHMs19AwDQ==", + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-20.3.0.tgz", + "integrity": "sha512-Y5wmYEwF1bl014Ps8QjagI911VbViQSFHSTVOCNSObdAzig9E5o6NOkoWe+doT1UZLrrInnlkrggQUsbtdKjOg==", "cpu": [ "arm64" ], @@ -5678,9 +5678,9 @@ } }, "node_modules/@nx/nx-linux-arm64-musl": { - "version": "20.1.4", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-20.1.4.tgz", - "integrity": "sha512-EaPUDqXvnPc/ure0x7N+5lRYvk5zqOQ3LzFOTRPWdqnFXejyTkGjZEYWbLFIJTFrvyEdpfaPTHyNmCHUrEz9TQ==", + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-20.3.0.tgz", + "integrity": "sha512-yGcIkmImyOMfPkQSYH2EVjPmFE0VkLcO71Bbkpr3RlJ1N/vjYxsGbdnqPiBb8Wshib/hmwpiMHf/yzQtKH0SQw==", "cpu": [ "arm64" ], @@ -5695,9 +5695,9 @@ } }, "node_modules/@nx/nx-linux-x64-gnu": { - "version": "20.1.4", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-20.1.4.tgz", - "integrity": "sha512-vaWV37ZayfyckVI/faWdQWIV9XQb06ZT8jHQnwgSd9tKbGz37vN30eYtgZlFL0P4bHfhjtmMXnLvADmfyO/KOw==", + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-20.3.0.tgz", + "integrity": "sha512-nkA2DLI+rpmiuiy7dyXP4l9s7dgHkQWDX7lG1XltiT41RzAReJF1h8qBE6XrsAYE1CtI76DRWVphnc93+iZr+A==", "cpu": [ "x64" ], @@ -5712,9 +5712,9 @@ } }, "node_modules/@nx/nx-linux-x64-musl": { - "version": "20.1.4", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-20.1.4.tgz", - "integrity": "sha512-wjq4Ea1oweBsIA9jq+jDT6BALxv/uac0aFykwoN23dOiwwSMFWMxbXUuBrxp0LjMFGV49S62kVDoRezukvkiZA==", + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-20.3.0.tgz", + "integrity": "sha512-sPMtTt9iTrCmFEIp9Qv27UX9PeL1aqKck2dz2TAFbXKVtF6+djOdTcNnTYw45KIP6izcUcOXXAq4G0QSQE7CLg==", "cpu": [ "x64" ], @@ -5729,9 +5729,9 @@ } }, "node_modules/@nx/nx-win32-arm64-msvc": { - "version": "20.1.4", - "resolved": "https://registry.npmjs.org/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-20.1.4.tgz", - "integrity": "sha512-d9jN8biyEJh4Mjdc3RU1j/+WIOjrO9mCDxYuERXP2ELaNsOk0tJgcXE1xsa9AF88AHGpOkCOS2rxy61DKBtFKg==", + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-20.3.0.tgz", + "integrity": "sha512-ppfNa/8OfpWA9o26Pz3vArN4ulAC+Hx70/ghPRCP7ed1Mb3Z6yR2Ry9KfBRImbqajvuAExM0TePKMGq9LCdXmg==", "cpu": [ "arm64" ], @@ -5746,9 +5746,9 @@ } }, "node_modules/@nx/nx-win32-x64-msvc": { - "version": "20.1.4", - "resolved": "https://registry.npmjs.org/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-20.1.4.tgz", - "integrity": "sha512-s3RwOkkWKzOflbTmc5MRc4EH2mk1AkJ/V8Gu3Qi2QncF9r1GrR7hDxROpu0MEoHfIhRG+d+n8OGX31nC9GZWUg==", + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-20.3.0.tgz", + "integrity": "sha512-8FOejZ4emtLSVn3pYWs4PIc3n4//qMbwMDPVxmPE8us3ir91Qh0bzr5zRj7Q8sEdSgvneXRXqtBp2grY2KMJsw==", "cpu": [ "x64" ], @@ -6721,105 +6721,32 @@ } }, "node_modules/@swimlane/ngx-graph": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@swimlane/ngx-graph/-/ngx-graph-8.4.0.tgz", - "integrity": "sha512-/A4FKyHU4ijnx8tGbTV5zBsMS+CH0/dA50Ee9oKPG8UCV3lJfUYr8+/UIwusrWC2YWXCgp/2A1VHFszCErrIaQ==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@swimlane/ngx-graph/-/ngx-graph-9.0.1.tgz", + "integrity": "sha512-kymJLcAicmw30tbcnEIobIz5Cv3f7RZvoe2MJVbBjVhRxEqFVs5SONILwcdQqGIrqJC9E2PUM9VFwm9Qpgq8Rg==", "license": "MIT", "dependencies": { - "d3-dispatch": "^1.0.3", - "d3-ease": "^1.0.5", - "d3-force": "^1.1.0", - "d3-scale": "^3.2.3", - "d3-selection": "^1.2.0", - "d3-shape": "^1.2.0", - "d3-timer": "^1.0.7", + "d3-dispatch": "^3.0.1", + "d3-ease": "^3.0.1", + "d3-force": "^3.0.0", + "d3-scale": "^4.0.2", + "d3-selection": "^3.0.0", + "d3-shape": "^3.2.0", + "d3-timer": "^3.0.1", "d3-transition": "^3.0.1", - "dagre": "^0.8.4", - "transformation-matrix": "^1.15.3", - "tslib": "^2.0.0", - "webcola": "^3.3.8" + "dagre": "^0.8.5", + "transformation-matrix": "^2.16.1", + "tslib": "^2.3.1", + "webcola": "^3.4.0" }, "peerDependencies": { - "@angular/animations": ">=10.0.0", - "@angular/cdk": ">=10.0.0", - "@angular/common": ">=10.0.0", - "@angular/core": ">=10.0.0", - "rxjs": ">=6.0.0" - } - }, - "node_modules/@swimlane/ngx-graph/node_modules/d3-array": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", - "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", - "license": "BSD-3-Clause", - "dependencies": { - "internmap": "^1.0.0" - } - }, - "node_modules/@swimlane/ngx-graph/node_modules/d3-ease": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.7.tgz", - "integrity": "sha512-lx14ZPYkhNx0s/2HX5sLFUI3mbasHjSSpwO/KaaNACweVwxUruKyWVcb293wMv1RqTPZyZ8kSZ2NogUZNcLOFQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@swimlane/ngx-graph/node_modules/d3-format": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-2.0.0.tgz", - "integrity": "sha512-Ab3S6XuE/Q+flY96HXT0jOXcM4EAClYFnRGY5zsjRGNy6qCYrQsMffs7cV5Q9xejb35zxW5hf/guKw34kvIKsA==", - "license": "BSD-3-Clause" - }, - "node_modules/@swimlane/ngx-graph/node_modules/d3-path": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", - "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", - "license": "BSD-3-Clause" - }, - "node_modules/@swimlane/ngx-graph/node_modules/d3-scale": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.3.0.tgz", - "integrity": "sha512-1JGp44NQCt5d1g+Yy+GeOnZP7xHo0ii8zsQp6PGzd+C1/dl0KGsp9A7Mxwp+1D1o4unbTTxVdU/ZOIEBoeZPbQ==", - "license": "BSD-3-Clause", - "dependencies": { - "d3-array": "^2.3.0", - "d3-format": "1 - 2", - "d3-interpolate": "1.2.0 - 2", - "d3-time": "^2.1.1", - "d3-time-format": "2 - 3" - } - }, - "node_modules/@swimlane/ngx-graph/node_modules/d3-shape": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", - "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", - "license": "BSD-3-Clause", - "dependencies": { - "d3-path": "1" - } - }, - "node_modules/@swimlane/ngx-graph/node_modules/d3-time": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-2.1.1.tgz", - "integrity": "sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ==", - "license": "BSD-3-Clause", - "dependencies": { - "d3-array": "2" - } - }, - "node_modules/@swimlane/ngx-graph/node_modules/d3-time-format": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-3.0.0.tgz", - "integrity": "sha512-UXJh6EKsHBTjopVqZBhFysQcoXSv/5yLONZvkQ5Kk3qbwiUYkdX17Xa1PT6U1ZWXGGfB1ey5L8dKMlFq2DO0Ag==", - "license": "BSD-3-Clause", - "dependencies": { - "d3-time": "1 - 2" + "@angular/animations": "15.x || 16.x || 17.x || 18.x", + "@angular/cdk": "15.x || 16.x || 17.x || 18.x", + "@angular/common": "15.x || 16.x || 17.x || 18.x", + "@angular/core": "15.x || 16.x || 17.x || 18.x", + "rxjs": "7.x" } }, - "node_modules/@swimlane/ngx-graph/node_modules/internmap": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", - "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", - "license": "ISC" - }, "node_modules/@testing-library/angular": { "version": "17.3.5", "resolved": "https://registry.npmjs.org/@testing-library/angular/-/angular-17.3.5.tgz", @@ -6858,20 +6785,6 @@ "node": ">=18" } }, - "node_modules/@testing-library/dom/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/@testing-library/dom/node_modules/aria-query": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", @@ -6883,41 +6796,17 @@ "dequal": "^2.0.3" } }, - "node_modules/@testing-library/dom/node_modules/pretty-format": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "node_modules/@ts-morph/common": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.22.0.tgz", + "integrity": "sha512-HqNBuV/oIlMKdkLshXd1zKBqNQCsuPEsgQOkfFQ/eUKjRlwndXW1AjN9LVkBEIukm00gGXSRmfkl0Wv5VXLnlw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@testing-library/dom/node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@ts-morph/common": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.23.0.tgz", - "integrity": "sha512-m7Lllj9n/S6sOkCkRftpM7L24uvmfXQFedlW/4hENcuJH1HHm9u5EgxZb9uVjQSCGrbBWBkOGgcTxNg36r6ywA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-glob": "^3.3.2", - "minimatch": "^9.0.3", - "mkdirp": "^3.0.1", - "path-browserify": "^1.0.1" + "fast-glob": "^3.3.2", + "minimatch": "^9.0.3", + "mkdirp": "^3.0.1", + "path-browserify": "^1.0.1" } }, "node_modules/@ts-morph/common/node_modules/mkdirp": { @@ -7151,9 +7040,9 @@ } }, "node_modules/@types/express-serve-static-core": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.2.tgz", - "integrity": "sha512-vluaspfvWEtE4vcSDlKRNer52DvOGrB2xv6diXy6UKyKW0lqZiWHGNApSyxOv+8DE5Z27IzVvE7hNkxg7EXIcg==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.3.tgz", + "integrity": "sha512-JEhMNwUJt7bw728CydvYzntD0XJeTmDnvwLlbfbAhE7Tbslm/ax6bdIiUwTgeVlZTsJQPwZwKpAkyDtIjsvx3g==", "dev": true, "license": "MIT", "dependencies": { @@ -7187,9 +7076,9 @@ } }, "node_modules/@types/hoist-non-react-statics": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz", - "integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==", + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.6.tgz", + "integrity": "sha512-lPByRJUer/iN/xa4qpyL0qmL11DqNW81iU/IG1S3uvRUq4oKagz8VCxZjiWkumgt66YT3vOdDgZ0o32sGKtCEw==", "license": "MIT", "dependencies": { "@types/react": "*", @@ -7251,6 +7140,41 @@ "pretty-format": "^29.0.0" } }, + "node_modules/@types/jest/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@types/jest/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@types/jest/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/jsdom": { "version": "20.0.1", "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz", @@ -7278,9 +7202,9 @@ "license": "MIT" }, "node_modules/@types/lodash": { - "version": "4.17.13", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.13.tgz", - "integrity": "sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg==", + "version": "4.17.14", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.14.tgz", + "integrity": "sha512-jsxagdikDiDBeIRaPYtArcT8my4tN1og7MtMRquFT3XNA6axxyHDRUemqDz/taRDdOUn0GnGHRCuff4q48sW9A==", "dev": true, "license": "MIT" }, @@ -7330,9 +7254,9 @@ } }, "node_modules/@types/node": { - "version": "22.10.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", - "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", + "version": "22.10.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.5.tgz", + "integrity": "sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7360,9 +7284,9 @@ } }, "node_modules/@types/prop-types": { - "version": "15.7.13", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", - "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==", + "version": "15.7.14", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", + "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", "license": "MIT" }, "node_modules/@types/qs": { @@ -7380,9 +7304,9 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "18.3.12", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz", - "integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==", + "version": "18.3.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz", + "integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==", "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -7549,17 +7473,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.18.1.tgz", - "integrity": "sha512-Ncvsq5CT3Gvh+uJG0Lwlho6suwDfUXH0HztslDf5I+F2wAFAZMRwYLEorumpKLzmO2suAXZ/td1tBg4NZIi9CQ==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.19.0.tgz", + "integrity": "sha512-NggSaEZCdSrFddbctrVjkVZvFC6KGfKfNK0CU7mNK/iKHGKbzT4Wmgm08dKpcZECBu9f5FypndoMyRHkdqfT1Q==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.18.1", - "@typescript-eslint/type-utils": "8.18.1", - "@typescript-eslint/utils": "8.18.1", - "@typescript-eslint/visitor-keys": "8.18.1", + "@typescript-eslint/scope-manager": "8.19.0", + "@typescript-eslint/type-utils": "8.19.0", + "@typescript-eslint/utils": "8.19.0", + "@typescript-eslint/visitor-keys": "8.19.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -7589,16 +7513,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.18.1.tgz", - "integrity": "sha512-rBnTWHCdbYM2lh7hjyXqxk70wvon3p2FyaniZuey5TrcGBpfhVp0OxOa6gxr9Q9YhZFKyfbEnxc24ZnVbbUkCA==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.19.0.tgz", + "integrity": "sha512-6M8taKyOETY1TKHp0x8ndycipTVgmp4xtg5QpEZzXxDhNvvHOJi5rLRkLr8SK3jTgD5l4fTlvBiRdfsuWydxBw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.18.1", - "@typescript-eslint/types": "8.18.1", - "@typescript-eslint/typescript-estree": "8.18.1", - "@typescript-eslint/visitor-keys": "8.18.1", + "@typescript-eslint/scope-manager": "8.19.0", + "@typescript-eslint/types": "8.19.0", + "@typescript-eslint/typescript-estree": "8.19.0", + "@typescript-eslint/visitor-keys": "8.19.0", "debug": "^4.3.4" }, "engines": { @@ -7614,14 +7538,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.18.1.tgz", - "integrity": "sha512-HxfHo2b090M5s2+/9Z3gkBhI6xBH8OJCFjH9MhQ+nnoZqxU3wNxkLT+VWXWSFWc3UF3Z+CfPAyqdCTdoXtDPCQ==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.19.0.tgz", + "integrity": "sha512-hkoJiKQS3GQ13TSMEiuNmSCvhz7ujyqD1x3ShbaETATHrck+9RaDdUbt+osXaUuns9OFwrDTTrjtwsU8gJyyRA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.18.1", - "@typescript-eslint/visitor-keys": "8.18.1" + "@typescript-eslint/types": "8.19.0", + "@typescript-eslint/visitor-keys": "8.19.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -7632,14 +7556,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.18.1.tgz", - "integrity": "sha512-jAhTdK/Qx2NJPNOTxXpMwlOiSymtR2j283TtPqXkKBdH8OAMmhiUfP0kJjc/qSE51Xrq02Gj9NY7MwK+UxVwHQ==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.19.0.tgz", + "integrity": "sha512-TZs0I0OSbd5Aza4qAMpp1cdCYVnER94IziudE3JU328YUHgWu9gwiwhag+fuLeJ2LkWLXI+F/182TbG+JaBdTg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.18.1", - "@typescript-eslint/utils": "8.18.1", + "@typescript-eslint/typescript-estree": "8.19.0", + "@typescript-eslint/utils": "8.19.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -7656,9 +7580,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.18.1.tgz", - "integrity": "sha512-7uoAUsCj66qdNQNpH2G8MyTFlgerum8ubf21s3TSM3XmKXuIn+H2Sifh/ES2nPOPiYSRJWAk0fDkW0APBWcpfw==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.19.0.tgz", + "integrity": "sha512-8XQ4Ss7G9WX8oaYvD4OOLCjIQYgRQxO+qCiR2V2s2GxI9AUpo7riNwo6jDhKtTcaJjT8PY54j2Yb33kWtSJsmA==", "dev": true, "license": "MIT", "engines": { @@ -7670,14 +7594,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.18.1.tgz", - "integrity": "sha512-z8U21WI5txzl2XYOW7i9hJhxoKKNG1kcU4RzyNvKrdZDmbjkmLBo8bgeiOJmA06kizLI76/CCBAAGlTlEeUfyg==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.19.0.tgz", + "integrity": "sha512-WW9PpDaLIFW9LCbucMSdYUuGeFUz1OkWYS/5fwZwTA+l2RwlWFdJvReQqMUMBw4yJWJOfqd7An9uwut2Oj8sLw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.18.1", - "@typescript-eslint/visitor-keys": "8.18.1", + "@typescript-eslint/types": "8.19.0", + "@typescript-eslint/visitor-keys": "8.19.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -7697,16 +7621,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.18.1.tgz", - "integrity": "sha512-8vikiIj2ebrC4WRdcAdDcmnu9Q/MXXwg+STf40BVfT8exDqBCUPdypvzcUPxEqRGKg9ALagZ0UWcYCtn+4W2iQ==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.19.0.tgz", + "integrity": "sha512-PTBG+0oEMPH9jCZlfg07LCB2nYI0I317yyvXGfxnvGvw4SHIOuRnQ3kadyyXY6tGdChusIHIbM5zfIbp4M6tCg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.18.1", - "@typescript-eslint/types": "8.18.1", - "@typescript-eslint/typescript-estree": "8.18.1" + "@typescript-eslint/scope-manager": "8.19.0", + "@typescript-eslint/types": "8.19.0", + "@typescript-eslint/typescript-estree": "8.19.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -7721,13 +7645,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.18.1.tgz", - "integrity": "sha512-Vj0WLm5/ZsD013YeUKn+K0y8p1M0jPpxOkKdbD1wB0ns53a5piVY02zjf072TblEweAbcYiFiPoSMF3kp+VhhQ==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.19.0.tgz", + "integrity": "sha512-mCFtBbFBJDCNCWUl5y6sZSCHXw1DEFEk3c/M3nRK2a4XUB8StGFtmcEMizdjKuBzB6e/smJAAWYug3VrdLMr1w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.18.1", + "@typescript-eslint/types": "8.19.0", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -8285,14 +8209,11 @@ } }, "node_modules/agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", "dev": true, "license": "MIT", - "dependencies": { - "debug": "^4.3.4" - }, "engines": { "node": ">= 14" } @@ -8513,6 +8434,16 @@ "dev": true, "license": "MIT" }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/autoprefixer": { "version": "10.4.20", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", @@ -8552,9 +8483,9 @@ } }, "node_modules/axios": { - "version": "1.7.8", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.8.tgz", - "integrity": "sha512-Uu0wb7KNqK2t5K+YQyVCLM76prD5sRFjKHbJYCP1J7JFGEQ6nN7HWn9+04LAeiJ3ji54lgS/gZCH1oxyrf1SPw==", + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", + "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", "dev": true, "license": "MIT", "dependencies": { @@ -8991,9 +8922,9 @@ } }, "node_modules/browserslist": { - "version": "4.24.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", - "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.3.tgz", + "integrity": "sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA==", "funding": [ { "type": "opencollective", @@ -9010,9 +8941,9 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001669", - "electron-to-chromium": "^1.5.41", - "node-releases": "^2.0.18", + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.1" }, "bin": { @@ -9166,17 +9097,47 @@ "license": "ISC" }, "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", "dev": true, "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", + "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "get-intrinsic": "^1.2.6" }, "engines": { "node": ">= 0.4" @@ -9215,9 +9176,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001685", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001685.tgz", - "integrity": "sha512-e/kJN1EMyHQzgcMEEgoo+YTCO1NGCmIYHk5Qk8jT6AazWemS5QFKJ5ShCJlH3GZrNIdZofcNCEwZqbMjjKzmnA==", + "version": "1.0.30001690", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001690.tgz", + "integrity": "sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w==", "funding": [ { "type": "opencollective", @@ -9552,9 +9513,9 @@ } }, "node_modules/code-block-writer": { - "version": "13.0.3", - "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.3.tgz", - "integrity": "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-12.0.0.tgz", + "integrity": "sha512-q4dMFMlXtKR3XNBHyMHt/3pwYNA69EDk00lloMOaaUMKPUXBw6lpXtbu3MMVG6/uOihGnRDOlkyqsONEUj60+w==", "dev": true, "license": "MIT" }, @@ -10091,12 +10052,6 @@ "node": ">=12" } }, - "node_modules/d3-collection": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.7.tgz", - "integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==", - "license": "BSD-3-Clause" - }, "node_modules/d3-color": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", @@ -10107,10 +10062,13 @@ } }, "node_modules/d3-dispatch": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.6.tgz", - "integrity": "sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA==", - "license": "BSD-3-Clause" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } }, "node_modules/d3-drag": { "version": "3.0.0", @@ -10135,15 +10093,17 @@ } }, "node_modules/d3-force": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-1.2.1.tgz", - "integrity": "sha512-HHvehyaiUlVo5CxBJ0yF/xny4xoaxFxDnBXNvNcfW9adORGZfyNF1dj6DGLKyk4Yh3brP/1h3rnDzdIAwL08zg==", - "license": "BSD-3-Clause", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", "dependencies": { - "d3-collection": "1", - "d3-dispatch": "1", - "d3-quadtree": "1", - "d3-timer": "1" + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" } }, "node_modules/d3-format": { @@ -10186,10 +10146,13 @@ } }, "node_modules/d3-quadtree": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.7.tgz", - "integrity": "sha512-RKPAeXnkC59IDGD0Wu5mANy0Q2V28L+fNe65pOCXVdVuTJS3WPKaJlFHer32Rbh9gIo9qMuJXio8ra4+YmIymA==", - "license": "BSD-3-Clause" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } }, "node_modules/d3-sankey": { "version": "0.12.3", @@ -10293,10 +10256,13 @@ } }, "node_modules/d3-timer": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.10.tgz", - "integrity": "sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw==", - "license": "BSD-3-Clause" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } }, "node_modules/d3-transition": { "version": "3.0.1", @@ -10680,9 +10646,9 @@ } }, "node_modules/domutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", - "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.1.tgz", + "integrity": "sha512-xWXmuRnN9OMP6ptPd2+H0cCbcYBULa5YDTbMm/2lvkWvNA3O4wcW+GvzooqBuNM8yy6pl3VIAeJTUUWUbfI5Fw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -10695,9 +10661,9 @@ } }, "node_modules/dotenv": { - "version": "16.4.5", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", - "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -10723,6 +10689,21 @@ "url": "https://dotenvx.com" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -10754,9 +10735,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.68", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.68.tgz", - "integrity": "sha512-FgMdJlma0OzUYlbrtZ4AeXjKxKPk6KT8WOP8BjcqxWtlg8qyJQjRzPJzUtUn5GBg1oQ26hFs7HOOHJMYiJRnvQ==", + "version": "1.5.76", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.76.tgz", + "integrity": "sha512-CjVQyG7n7Sr+eBXE86HIulnL5N8xZY1sgmOPGuq/F0Rr0FJq63lg0kEtOIDfZBk44FnDLf6FUJ+dsJcuiUDdDQ==", "license": "ISC" }, "node_modules/emittery": { @@ -10779,9 +10760,9 @@ "license": "MIT" }, "node_modules/emoji-js": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/emoji-js/-/emoji-js-3.8.0.tgz", - "integrity": "sha512-A5FNHKlRPRo6RJWrrdGWnoolIBMkVXHy4qkO0V5ahekQPjfVECxvOOWADeAF/SbzRVA9Sxdj24FCoRYGt06skA==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/emoji-js/-/emoji-js-3.8.1.tgz", + "integrity": "sha512-yyXMnZLXgqQHAhEm2DKK4Nrca+jbLQfNOP2mLcNTS6XxzzbQLDFHAguPQrtJS4Udot0Pvomwmh1ckQzhrePhKw==", "license": "MIT", "dependencies": { "emoji-datasource": "15.0.1" @@ -10850,9 +10831,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.17.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", - "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz", + "integrity": "sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==", "dev": true, "license": "MIT", "dependencies": { @@ -10943,14 +10924,11 @@ } }, "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "dev": true, "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, "engines": { "node": ">= 0.4" } @@ -10966,12 +10944,25 @@ } }, "node_modules/es-module-lexer": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", - "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", + "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==", "dev": true, "license": "MIT" }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.0.tgz", @@ -12016,16 +12007,26 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.3.tgz", - "integrity": "sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.4.tgz", + "integrity": "sha512-G3iTQw1DizJQ5eEqj1CbFCWhq+pzum7qepkxU7rS1FGZDqjYKcrguo9XDRbV7EgPnn8CgaPigTq+NEjyioeYZQ==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], "license": "BSD-3-Clause" }, "node_modules/fastq": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.18.0.tgz", + "integrity": "sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==", "license": "ISC", "dependencies": { "reusify": "^1.0.4" @@ -12190,6 +12191,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/find-yarn-workspace-root": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz", + "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "micromatch": "^4.0.2" + } + }, "node_modules/flat": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", @@ -12222,14 +12233,14 @@ "license": "ISC" }, "node_modules/folder-hash": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/folder-hash/-/folder-hash-4.0.4.tgz", - "integrity": "sha512-zEyYH+UsHEfJJcCRSf9ai5I4CTZwZ8ObONRuEI5hcEmJY5pS0FUWKruX9mMnYJrgC7MlPFDYnGsK1R+WFYjLlQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/folder-hash/-/folder-hash-4.1.0.tgz", + "integrity": "sha512-45DBqW/wE1FdEynQ922zoKsICdAkZEkuyWijChitENaoI+AF/FsTlKOB+EwaEqhNfBZ5dEgLVv694wbpNMIC5g==", "dev": true, "license": "MIT", "dependencies": { - "debug": "^4.3.3", - "minimatch": "~5.1.2" + "debug": "4.4.0", + "minimatch": "10.0.1" }, "bin": { "folder-hash": "bin/folder-hash" @@ -12239,16 +12250,19 @@ } }, "node_modules/folder-hash/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=10" + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/follow-redirects": { @@ -12399,6 +12413,22 @@ "dev": true, "license": "MIT" }, + "node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/fs-minipass": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", @@ -12476,17 +12506,22 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", + "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", "dev": true, "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.0", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -12505,6 +12540,20 @@ "node": ">=8.0.0" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -12649,14 +12698,11 @@ "license": "MIT" }, "node_modules/gopd": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.1.0.tgz", - "integrity": "sha512-FQoVQnqcdk4hVM4JN1eromaun4iuS34oStkdlLENLdpULsuQcTyXj8w7ayhuUfPwEYZ1ZOooOTT6fdA9Vmx/RA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "dev": true, "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, "engines": { "node": ">= 0.4" }, @@ -12726,22 +12772,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-proto": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.1.0.tgz", - "integrity": "sha512-QLdzI9IIO1Jg7f9GT1gXpPpXArAn6cS31R1eEZqz08Gc+uQ8/XiqHWt17Fiw+2p6oTTIq5GXEpQkAlA88YRl/Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -12769,9 +12799,9 @@ } }, "node_modules/highlight.js": { - "version": "11.10.0", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.10.0.tgz", - "integrity": "sha512-SYVnVFswQER+zu1laSya563s+F8VDGt7o35d4utbamowvUNLLMovFqwCLSocpZTz3MgaSRA1IbqRWZv97dtErQ==", + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", + "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", "license": "BSD-3-Clause", "engines": { "node": ">=12.0.0" @@ -13319,9 +13349,9 @@ } }, "node_modules/is-core-module": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", - "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "dev": true, "license": "MIT", "dependencies": { @@ -13545,9 +13575,10 @@ } }, "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, "license": "MIT" }, "node_modules/isexe": { @@ -13798,26 +13829,61 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-circus/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "node_modules/jest-circus/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-cli": { + "node_modules/jest-circus/node_modules/pretty-format": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", - "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-circus/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", "chalk": "^4.0.0", "create-jest": "^29.7.0", "exit": "^0.1.2", @@ -13888,6 +13954,41 @@ } } }, + "node_modules/jest-config/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-config/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-config/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, "node_modules/jest-config/node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -13921,6 +14022,41 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-diff/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-diff/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, "node_modules/jest-docblock": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", @@ -13951,6 +14087,41 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-each/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-each/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, "node_modules/jest-environment-jsdom": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz", @@ -14102,6 +14273,41 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-leak-detector/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-leak-detector/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-leak-detector/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, "node_modules/jest-matcher-utils": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", @@ -14118,6 +14324,41 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-matcher-utils/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, "node_modules/jest-message-util": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", @@ -14139,6 +14380,41 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-message-util/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, "node_modules/jest-message-util/node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -14210,6 +14486,41 @@ "typescript": ">=4.8" } }, + "node_modules/jest-preset-angular/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-preset-angular/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-preset-angular/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, "node_modules/jest-regex-util": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", @@ -14395,6 +14706,41 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-snapshot/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-snapshot/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, "node_modules/jest-util": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", @@ -14444,6 +14790,19 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-validate/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/jest-validate/node_modules/camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", @@ -14457,6 +14816,28 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/jest-validate/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, "node_modules/jest-watcher": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", @@ -14510,9 +14891,9 @@ } }, "node_modules/jiti": { - "version": "1.21.6", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", - "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "dev": true, "license": "MIT", "bin": { @@ -14628,6 +15009,26 @@ "dev": true, "license": "MIT" }, + "node_modules/json-stable-stringify": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.2.1.tgz", + "integrity": "sha512-Lp6HbbBgosLmJbjx0pBLbgvx68FaFU1sdkmBuckmhhJ88kL13OA51CDtR2yJB50eCNMH9wRqtQNNiAqQH4YXnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "isarray": "^2.0.5", + "jsonify": "^0.0.1", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", @@ -14654,6 +15055,29 @@ "dev": true, "license": "MIT" }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz", + "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==", + "dev": true, + "license": "Public Domain", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/jsonparse": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", @@ -14687,9 +15111,9 @@ } }, "node_modules/katex": { - "version": "0.16.11", - "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.11.tgz", - "integrity": "sha512-RQrI8rlHY92OLf3rho/Ts8i/XvjgguEjOkO1BEXcU3N8BqPpSzBNwV/G0Ukr+P/l3ivvJUE/Fa/CwbS6HesGNQ==", + "version": "0.16.19", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.19.tgz", + "integrity": "sha512-3IA6DYVhxhBabjSLTNO9S4+OliA3Qvb8pBQXMfC4WxXJgLwZgnfDl0BmB4z6nBMdznBsZ+CGM8DrGZ5hcguDZg==", "funding": [ "https://opencollective.com/katex", "https://github.com/sponsors/katex" @@ -14722,6 +15146,16 @@ "node": ">=0.10.0" } }, + "node_modules/klaw-sync": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", + "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.11" + } + }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -14907,13 +15341,13 @@ } }, "node_modules/lint-staged": { - "version": "15.2.11", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.11.tgz", - "integrity": "sha512-Ev6ivCTYRTGs9ychvpVw35m/bcNDuBN+mnTeObCL5h+boS5WzBEC6LHI4I9F/++sZm1m+J2LEiy0gxL/R9TBqQ==", + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.3.0.tgz", + "integrity": "sha512-vHFahytLoF2enJklgtOtCtIjZrKD/LoxlaUusd5nh7dWv/dkKQJY74ndFSzxCdv7g0ueGg1ORgTSt4Y9LPZn9A==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "~5.3.0", + "chalk": "~5.4.1", "commander": "~12.1.0", "debug": "~4.4.0", "execa": "~8.0.1", @@ -14961,9 +15395,9 @@ } }, "node_modules/lint-staged/node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", "dev": true, "license": "MIT", "engines": { @@ -15643,6 +16077,16 @@ "integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==", "license": "ISC" }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/mdurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", @@ -15660,9 +16104,9 @@ } }, "node_modules/memfs": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.14.1.tgz", - "integrity": "sha512-Fq5CMEth+2iprLJ5mNizRcWuiwRZYjNkUD0zKk224jZunE9CRacTRDK8QLALbMBlNX2y3nY6lKZbesCwDwacig==", + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.15.3.tgz", + "integrity": "sha512-vR/g1SgqvKJgAyYla+06G4p/EOcEmwhYuVb1yc1ixcKf8o/sh7Zngv63957ZSNd1xrZJoinmNyDf2LzuP8WJXw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -16294,6 +16738,53 @@ } } }, + "node_modules/ngxtension/node_modules/@ts-morph/common": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.23.0.tgz", + "integrity": "sha512-m7Lllj9n/S6sOkCkRftpM7L24uvmfXQFedlW/4hENcuJH1HHm9u5EgxZb9uVjQSCGrbBWBkOGgcTxNg36r6ywA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "^3.3.2", + "minimatch": "^9.0.3", + "mkdirp": "^3.0.1", + "path-browserify": "^1.0.1" + } + }, + "node_modules/ngxtension/node_modules/code-block-writer": { + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.3.tgz", + "integrity": "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==", + "dev": true, + "license": "MIT" + }, + "node_modules/ngxtension/node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ngxtension/node_modules/ts-morph": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-22.0.0.tgz", + "integrity": "sha512-M9MqFGZREyeb5fTl6gNHKZLqBQA0TjA1lea+CR48R8EBTDuWrNqW6ccC5QvjNR4s6wDumD3LTCjOFSp9iwlzaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ts-morph/common": "~0.23.0", + "code-block-writer": "^13.0.1" + } + }, "node_modules/nice-napi": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz", @@ -16449,9 +16940,9 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", - "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", "license": "MIT" }, "node_modules/nopt": { @@ -16640,9 +17131,9 @@ "license": "MIT" }, "node_modules/nx": { - "version": "20.1.4", - "resolved": "https://registry.npmjs.org/nx/-/nx-20.1.4.tgz", - "integrity": "sha512-hyvGYxTzBkPxSXAB2tuqdv9TpVde5xOdGalsIdhF7j7PI3nwPpqtc3y28YTgRgpxtOE1Y6BfDNkXMO1SW0xu2w==", + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/nx/-/nx-20.3.0.tgz", + "integrity": "sha512-Nzi4k7tV22zwO2iBLk+pHxorLEWPJpPrVCACtz0SQ63j/LiAgfhoqruJO+VU+V+E9qdyPsvmqIL/Iaf/GRQlqA==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -16671,12 +17162,14 @@ "npm-run-path": "^4.0.1", "open": "^8.4.0", "ora": "5.3.0", + "resolve.exports": "2.0.3", "semver": "^7.5.3", "string-width": "^4.2.3", "tar-stream": "~2.2.0", "tmp": "~0.2.1", "tsconfig-paths": "^4.1.2", "tslib": "^2.3.0", + "yaml": "^2.6.0", "yargs": "^17.6.2", "yargs-parser": "21.1.1" }, @@ -16685,16 +17178,16 @@ "nx-cloud": "bin/nx-cloud.js" }, "optionalDependencies": { - "@nx/nx-darwin-arm64": "20.1.4", - "@nx/nx-darwin-x64": "20.1.4", - "@nx/nx-freebsd-x64": "20.1.4", - "@nx/nx-linux-arm-gnueabihf": "20.1.4", - "@nx/nx-linux-arm64-gnu": "20.1.4", - "@nx/nx-linux-arm64-musl": "20.1.4", - "@nx/nx-linux-x64-gnu": "20.1.4", - "@nx/nx-linux-x64-musl": "20.1.4", - "@nx/nx-win32-arm64-msvc": "20.1.4", - "@nx/nx-win32-x64-msvc": "20.1.4" + "@nx/nx-darwin-arm64": "20.3.0", + "@nx/nx-darwin-x64": "20.3.0", + "@nx/nx-freebsd-x64": "20.3.0", + "@nx/nx-linux-arm-gnueabihf": "20.3.0", + "@nx/nx-linux-arm64-gnu": "20.3.0", + "@nx/nx-linux-arm64-musl": "20.3.0", + "@nx/nx-linux-x64-gnu": "20.3.0", + "@nx/nx-linux-x64-musl": "20.3.0", + "@nx/nx-win32-arm64-msvc": "20.3.0", + "@nx/nx-win32-x64-msvc": "20.3.0" }, "peerDependencies": { "@swc-node/register": "^1.8.0", @@ -16920,6 +17413,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/obuf": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", @@ -17332,6 +17835,93 @@ "node": ">= 0.8" } }, + "node_modules/patch-package": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.0.tgz", + "integrity": "sha512-da8BVIhzjtgScwDJ2TtKsfT5JFWz1hYoBl9rUQ1f38MC2HwnEIkK8VN3dKMKcP7P7bvvgzNDbfNHtx3MsQb5vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@yarnpkg/lockfile": "^1.1.0", + "chalk": "^4.1.2", + "ci-info": "^3.7.0", + "cross-spawn": "^7.0.3", + "find-yarn-workspace-root": "^2.0.0", + "fs-extra": "^9.0.0", + "json-stable-stringify": "^1.0.2", + "klaw-sync": "^6.0.0", + "minimist": "^1.2.6", + "open": "^7.4.2", + "rimraf": "^2.6.3", + "semver": "^7.5.3", + "slash": "^2.0.0", + "tmp": "^0.0.33", + "yaml": "^2.2.2" + }, + "bin": { + "patch-package": "index.js" + }, + "engines": { + "node": ">=14", + "npm": ">5" + } + }, + "node_modules/patch-package/node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/patch-package/node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/patch-package/node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/patch-package/node_modules/slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/path-browserify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", @@ -17459,15 +18049,15 @@ "license": "0BSD" }, "node_modules/pdfjs-dist": { - "version": "4.9.155", - "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-4.9.155.tgz", - "integrity": "sha512-epRZn6DQQKCOEqbmFsxkiMBm1MHaNrnr6T4VBNP0bsDvdJdmrWcZbS5cgJXW68P0d3uJTlFhF6Wms2tlSgPYig==", + "version": "4.10.38", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-4.10.38.tgz", + "integrity": "sha512-/Y3fcFrXEAsMjJXeL9J8+ZG9U01LbuWaYypvDW2ycW1jL269L3js3DVBjDJ0Up9Np1uqDXsDrRihHANhZOlwdQ==", "license": "Apache-2.0", "engines": { "node": ">=20" }, "optionalDependencies": { - "@napi-rs/canvas": "^0.1.64" + "@napi-rs/canvas": "^0.1.65" } }, "node_modules/pepjs": { @@ -17725,9 +18315,9 @@ } }, "node_modules/postcss-modules-local-by-default": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.1.0.tgz", - "integrity": "sha512-rm0bdSv4jC3BDma3s9H19ZddW0aHX6EoqwDYU2IfZhRN+53QrufTRo2IdkAbRqLx4R2IYbZnbjKKxg4VN5oU9Q==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", + "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", "dev": true, "license": "MIT", "dependencies": { @@ -17834,18 +18424,19 @@ } }, "node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@jest/schemas": "^29.6.3", + "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" + "react-is": "^17.0.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, "node_modules/pretty-format/node_modules/ansi-styles": { @@ -17854,6 +18445,7 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10" }, @@ -17861,6 +18453,14 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/pretty-format/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/proc-log": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", @@ -18139,10 +18739,11 @@ } }, "node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "license": "MIT" + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.0.0.tgz", + "integrity": "sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==", + "license": "MIT", + "peer": true }, "node_modules/react-redux": { "version": "8.1.3", @@ -18183,6 +18784,12 @@ } } }, + "node_modules/react-redux/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, "node_modules/react-tooltip": { "version": "4.5.1", "resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-4.5.1.tgz", @@ -18233,6 +18840,12 @@ "util-deprecate": "~1.0.1" } }, + "node_modules/readable-stream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -18805,9 +19418,9 @@ "license": "MIT" }, "node_modules/sass": { - "version": "1.83.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.83.0.tgz", - "integrity": "sha512-qsSxlayzoOjdvXMVLkzF84DJFc2HZEL/rFyGIKbbilYtAvlCxyuzUeff9LawTn4btVnLKg75Z8MMr1lxU1lfGw==", + "version": "1.83.1", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.83.1.tgz", + "integrity": "sha512-EVJbDaEs4Rr3F0glJzFSOvtg2/oy2V/YrGFPqPY24UqcLDWcI9ZY5sN+qyO3c/QCZwzgfirvhXvINiJCE/OLcA==", "dev": true, "license": "MIT", "dependencies": { @@ -18867,9 +19480,9 @@ } }, "node_modules/sass/node_modules/chokidar": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", - "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "dev": true, "license": "MIT", "dependencies": { @@ -18927,9 +19540,9 @@ } }, "node_modules/schema-utils": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", - "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz", + "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==", "dev": true, "license": "MIT", "dependencies": { @@ -18939,7 +19552,7 @@ "ajv-keywords": "^5.1.0" }, "engines": { - "node": ">= 12.13.0" + "node": ">= 10.13.0" }, "funding": { "type": "opencollective", @@ -19248,16 +19861,73 @@ } }, "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -19437,13 +20107,13 @@ } }, "node_modules/socks-proxy-agent": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz", - "integrity": "sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==", + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", "dev": true, "license": "MIT", "dependencies": { - "agent-base": "^7.1.1", + "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" }, @@ -20088,17 +20758,17 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.10", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", - "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", + "version": "5.3.11", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.11.tgz", + "integrity": "sha512-RVCsMfuD0+cTt3EwX8hSl2Ks56EbFHWmhluwcqoPKtBnfjiT6olaq7PRIRfhyU8nnC2MrnDrBLfrD/RGE+cVXQ==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.20", + "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.1", - "terser": "^5.26.0" + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" }, "engines": { "node": ">= 10.13.0" @@ -20122,33 +20792,6 @@ } } }, - "node_modules/terser-webpack-plugin/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "ajv": "^6.9.1" - } - }, "node_modules/terser-webpack-plugin/node_modules/jest-worker": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", @@ -20164,32 +20807,6 @@ "node": ">= 10.13.0" } }, - "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/terser-webpack-plugin/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, "node_modules/terser-webpack-plugin/node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -20286,9 +20903,9 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.1.tgz", - "integrity": "sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", "dev": true, "license": "MIT" }, @@ -20333,22 +20950,22 @@ } }, "node_modules/tldts": { - "version": "6.1.65", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.65.tgz", - "integrity": "sha512-xU9gLTfAGsADQ2PcWee6Hg8RFAv0DnjMGVJmDnUmI8a9+nYmapMQix4afwrdaCtT+AqP4MaxEzu7cCrYmBPbzQ==", + "version": "6.1.70", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.70.tgz", + "integrity": "sha512-/W1YVgYVJd9ZDjey5NXadNh0mJXkiUMUue9Zebd0vpdo1sU+H4zFFTaJ1RKD4N6KFoHfcXy6l+Vu7bh+bdWCzA==", "dev": true, "license": "MIT", "dependencies": { - "tldts-core": "^6.1.65" + "tldts-core": "^6.1.70" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/tldts-core": { - "version": "6.1.65", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.65.tgz", - "integrity": "sha512-Uq5t0N0Oj4nQSbU8wFN1YYENvMthvwU13MQrMJRspYCGLSAZjAfoBOJki5IQpnBM/WFskxxC/gIOTwaedmHaSg==", + "version": "6.1.70", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.70.tgz", + "integrity": "sha512-RNnIXDB1FD4T9cpQRErEqw6ZpjLlGdMOitdV+0xtbsnwr4YFka1zpc7D4KD+aAn8oSG5JyFrdasZTE04qDE9Yg==", "dev": true, "license": "MIT" }, @@ -20421,10 +21038,13 @@ } }, "node_modules/transformation-matrix": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/transformation-matrix/-/transformation-matrix-1.15.3.tgz", - "integrity": "sha512-ThJH58GNFKhCw3gIoOtwf3tNwuYjbyEeiGdeq4mNMYWdJctnI896KUqn6PVt7jmNVepqa1bcKQtnMB1HtjsDMA==", - "license": "MIT" + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/transformation-matrix/-/transformation-matrix-2.16.1.tgz", + "integrity": "sha512-tdtC3wxVEuzU7X/ydL131Q3JU5cPMEn37oqVLITjRDSDsnSHVFzW2JiCLfZLIQEgWzZHdSy3J6bZzvKEN24jGA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/chrvadala" + } }, "node_modules/tree-dump": { "version": "1.0.2", @@ -20539,14 +21159,14 @@ } }, "node_modules/ts-morph": { - "version": "22.0.0", - "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-22.0.0.tgz", - "integrity": "sha512-M9MqFGZREyeb5fTl6gNHKZLqBQA0TjA1lea+CR48R8EBTDuWrNqW6ccC5QvjNR4s6wDumD3LTCjOFSp9iwlzaw==", + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-21.0.1.tgz", + "integrity": "sha512-dbDtVdEAncKctzrVZ+Nr7kHpHkv+0JDJb2MjjpBaj8bFeCkePU9rHfMklmhuLFnpeq/EJZk2IhStY6NzqgjOkg==", "dev": true, "license": "MIT", "dependencies": { - "@ts-morph/common": "~0.23.0", - "code-block-writer": "^13.0.1" + "@ts-morph/common": "~0.22.0", + "code-block-writer": "^12.0.0" } }, "node_modules/ts-node": { @@ -20795,6 +21415,29 @@ "typescript-logic": "^0.0.0" } }, + "node_modules/typescript-eslint": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.19.0.tgz", + "integrity": "sha512-Ni8sUkVWYK4KAcTtPjQ/UTiRk6jcsuDhPpxULapUDi8A/l8TSBk+t1GtJA1RsCzIJg0q6+J7bf35AwQigENWRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.19.0", + "@typescript-eslint/parser": "8.19.0", + "@typescript-eslint/utils": "8.19.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" + } + }, "node_modules/typescript-logic": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/typescript-logic/-/typescript-logic-0.0.0.tgz", @@ -20920,6 +21563,16 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -20981,12 +21634,12 @@ } }, "node_modules/use-sync-external-store": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz", - "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz", + "integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==", "license": "MIT", "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "node_modules/util-deprecate": { @@ -21006,9 +21659,9 @@ } }, "node_modules/uuid": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.3.tgz", - "integrity": "sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==", + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.4.tgz", + "integrity": "sha512-IzL6VtTTYcAhA/oghbFJ1Dkmqev+FpQWnCBaKq/gUluLxliWvO8DPFWfIviRmYbtaavtSQe4WBL++rFjdcGWEg==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" @@ -21813,6 +22466,12 @@ "d3-timer": "^1.0.5" } }, + "node_modules/webcola/node_modules/d3-dispatch": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.6.tgz", + "integrity": "sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA==", + "license": "BSD-3-Clause" + }, "node_modules/webcola/node_modules/d3-drag": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-1.2.5.tgz", @@ -21829,6 +22488,12 @@ "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", "license": "BSD-3-Clause" }, + "node_modules/webcola/node_modules/d3-selection": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.2.tgz", + "integrity": "sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg==", + "license": "BSD-3-Clause" + }, "node_modules/webcola/node_modules/d3-shape": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", @@ -21838,6 +22503,12 @@ "d3-path": "1" } }, + "node_modules/webcola/node_modules/d3-timer": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.10.tgz", + "integrity": "sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw==", + "license": "BSD-3-Clause" + }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -22204,9 +22875,9 @@ } }, "node_modules/whatwg-url": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.0.0.tgz", - "integrity": "sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.1.0.tgz", + "integrity": "sha512-jlf/foYIKywAt3x/XWKZ/3rz8OSJPiWktjmk891alJUEjiVxKX9LEO92qH3hv4aJ0mN3MWPvGMCy8jQi95xK4w==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 7c7e3e405edd..d392de262aa4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "artemis", - "version": "7.8.1", + "version": "7.8.3", "description": "Interactive Learning with Individual Feedback", "private": true, "license": "MIT", @@ -14,13 +14,13 @@ ], "dependencies": { "@angular/animations": "18.2.13", - "@angular/cdk": "18.2.13", + "@angular/cdk": "18.2.14", "@angular/common": "18.2.13", "@angular/compiler": "18.2.13", "@angular/core": "18.2.13", "@angular/forms": "18.2.13", "@angular/localize": "18.2.13", - "@angular/material": "18.2.13", + "@angular/material": "18.2.14", "@angular/platform-browser": "18.2.13", "@angular/platform-browser-dynamic": "18.2.13", "@angular/router": "18.2.13", @@ -39,7 +39,7 @@ "@sentry/angular": "8.47.0", "@siemens/ngx-datatable": "22.4.1", "@swimlane/ngx-charts": "21.1.2", - "@swimlane/ngx-graph": "8.4.0", + "@swimlane/ngx-graph": "9.0.1", "@vscode/codicons": "0.0.36", "@vscode/markdown-it-katex": "1.1.1", "bootstrap": "5.3.3", @@ -49,7 +49,7 @@ "dayjs": "1.11.13", "diff-match-patch-typescript": "1.1.0", "dompurify": "3.2.3", - "emoji-js": "3.8.0", + "emoji-js": "3.8.1", "export-to-csv": "1.4.0", "fast-json-patch": "3.1.1", "franc-min": "6.2.0", @@ -68,7 +68,7 @@ "ngx-webstorage": "18.0.0", "papaparse": "5.4.1", "pdf-lib": "1.17.1", - "pdfjs-dist": "4.9.155", + "pdfjs-dist": "4.10.38", "rxjs": "7.8.1", "simple-statistics": "7.8.7", "smoothscroll-polyfill": "0.4.4", @@ -77,19 +77,12 @@ "ts-cacheable": "1.0.10", "tslib": "2.8.1", "turndown": "7.2.0", - "uuid": "11.0.3", + "uuid": "11.0.4", "webstomp-client": "1.2.6", "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz", "zone.js": "0.14.10" }, "overrides": { - "@swimlane/ngx-graph": { - "d3-brush": "^3.0.0", - "d3-color": "^3.1.0", - "d3-interpolate": "^3.0.1", - "d3-selection": "^3.0.0", - "d3-transition": "^3.0.1" - }, "@typescript-eslint/utils": { "eslint": "^9.17.0" }, @@ -114,14 +107,14 @@ "yargs-parser": "21.1.1" }, "devDependencies": { - "@analogjs/vite-plugin-angular": "1.10.3", + "@analogjs/vite-plugin-angular": "1.11.0", "@angular-builders/jest": "18.0.0", "@angular-devkit/build-angular": "18.2.12", - "@angular-eslint/builder": "18.4.1", - "@angular-eslint/eslint-plugin": "18.4.1", - "@angular-eslint/eslint-plugin-template": "18.4.1", - "@angular-eslint/schematics": "18.4.1", - "@angular-eslint/template-parser": "18.4.1", + "@angular-eslint/builder": "18.4.3", + "@angular-eslint/eslint-plugin": "18.4.3", + "@angular-eslint/eslint-plugin-template": "18.4.3", + "@angular-eslint/schematics": "18.4.3", + "@angular-eslint/template-parser": "18.4.3", "@angular/cli": "18.2.12", "@angular/compiler-cli": "18.2.13", "@angular/language-service": "18.2.13", @@ -134,21 +127,21 @@ "@types/jest": "29.5.14", "@types/lodash-es": "4.17.12", "@types/markdown-it": "14.1.2", - "@types/node": "22.10.2", + "@types/node": "22.10.5", "@types/papaparse": "5.3.15", "@types/smoothscroll-polyfill": "0.3.4", "@types/sockjs-client": "1.5.4", "@types/turndown": "5.0.5", "@types/uuid": "10.0.0", - "@typescript-eslint/eslint-plugin": "8.18.1", - "@typescript-eslint/parser": "8.18.1", + "@typescript-eslint/eslint-plugin": "8.19.0", + "@typescript-eslint/parser": "8.19.0", "eslint": "9.17.0", "eslint-config-prettier": "9.1.0", "eslint-plugin-deprecation": "3.0.0", "eslint-plugin-jest": "28.10.0", "eslint-plugin-jest-extended": "2.4.0", "eslint-plugin-prettier": "5.2.1", - "folder-hash": "4.0.4", + "folder-hash": "4.1.0", "husky": "9.1.7", "jest": "29.7.0", "jest-canvas-mock": "2.5.2", @@ -157,14 +150,16 @@ "jest-fail-on-console": "3.3.1", "jest-junit": "16.0.0", "jest-preset-angular": "14.4.2", - "lint-staged": "15.2.11", + "lint-staged": "15.3.0", "ng-mocks": "14.13.1", "ngxtension": "4.2.0", + "patch-package": "8.0.0", "prettier": "3.4.2", "rimraf": "6.0.1", - "sass": "1.83.0", + "sass": "1.83.1", "ts-jest": "29.2.5", "typescript": "5.5.4", + "typescript-eslint": "8.19.0", "vite-tsconfig-paths": "5.1.4", "vitest": "2.1.8", "weak-napi": "2.0.2" @@ -179,7 +174,7 @@ "compile:ts:tests": "npm run prebuild && tsc --project tsconfig.spec.json", "lint": "eslint", "lint:fix": "eslint --fix", - "postinstall": "husky", + "postinstall": "husky && patch-package", "prebuild": "node prebuild.mjs", "prepare": "husky", "prettier:check": "prettier --check \"src/{main/webapp,test}/**/*.{json,ts,js,css,scss,html}\"", diff --git a/patches/@swimlane+ngx-graph+9.0.1.patch b/patches/@swimlane+ngx-graph+9.0.1.patch new file mode 100644 index 000000000000..2843ab5eb28d --- /dev/null +++ b/patches/@swimlane+ngx-graph+9.0.1.patch @@ -0,0 +1,26 @@ +diff --git a/node_modules/@swimlane/ngx-graph/esm2022/lib/graph/graph.component.mjs b/node_modules/@swimlane/ngx-graph/esm2022/lib/graph/graph.component.mjs +index fadcfa7..6a6ab52 100644 +--- a/node_modules/@swimlane/ngx-graph/esm2022/lib/graph/graph.component.mjs ++++ b/node_modules/@swimlane/ngx-graph/esm2022/lib/graph/graph.component.mjs +@@ -197,7 +197,7 @@ export class GraphComponent { + if (layoutSettings) { + this.setLayoutSettings(this.layoutSettings); + } +- if (this.layout && this.nodes.length && this.links.length) { ++ if (this.layout && this.nodes && this.links) { + this.update(); + } + } +diff --git a/node_modules/@swimlane/ngx-graph/fesm2022/swimlane-ngx-graph.mjs b/node_modules/@swimlane/ngx-graph/fesm2022/swimlane-ngx-graph.mjs +index 0e851a7..c8dce60 100644 +--- a/node_modules/@swimlane/ngx-graph/fesm2022/swimlane-ngx-graph.mjs ++++ b/node_modules/@swimlane/ngx-graph/fesm2022/swimlane-ngx-graph.mjs +@@ -1478,7 +1478,7 @@ class GraphComponent { + if (layoutSettings) { + this.setLayoutSettings(this.layoutSettings); + } +- if (this.layout && this.nodes.length && this.links.length) { ++ if (this.layout && this.nodes && this.links) { + this.update(); + } + } diff --git a/src/main/java/de/tum/cit/aet/artemis/assessment/domain/Feedback.java b/src/main/java/de/tum/cit/aet/artemis/assessment/domain/Feedback.java index de095b019216..c07579401ff0 100644 --- a/src/main/java/de/tum/cit/aet/artemis/assessment/domain/Feedback.java +++ b/src/main/java/de/tum/cit/aet/artemis/assessment/domain/Feedback.java @@ -79,7 +79,7 @@ public class Feedback extends DomainObject { * null if the feedback was not created by an automatic test case. */ @ManyToOne(fetch = FetchType.LAZY) - @JsonIgnoreProperties({ "tasks", "solutionEntries", "exercise", "coverageEntries" }) + @JsonIgnoreProperties({ "tasks", "exercise" }) private ProgrammingExerciseTestCase testCase; /** diff --git a/src/main/java/de/tum/cit/aet/artemis/assessment/domain/Result.java b/src/main/java/de/tum/cit/aet/artemis/assessment/domain/Result.java index 88d5d44e5271..ad45634f2d74 100644 --- a/src/main/java/de/tum/cit/aet/artemis/assessment/domain/Result.java +++ b/src/main/java/de/tum/cit/aet/artemis/assessment/domain/Result.java @@ -10,10 +10,8 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.Set; import java.util.stream.Collectors; import jakarta.persistence.CascadeType; @@ -28,7 +26,6 @@ import jakarta.persistence.OneToMany; import jakarta.persistence.OrderColumn; import jakarta.persistence.Table; -import jakarta.persistence.Transient; import jakarta.validation.constraints.NotNull; import org.apache.commons.lang3.StringUtils; @@ -53,7 +50,6 @@ import de.tum.cit.aet.artemis.exercise.domain.participation.Participation; import de.tum.cit.aet.artemis.exercise.service.ExerciseDateService; import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise; -import de.tum.cit.aet.artemis.programming.domain.hestia.CoverageFileReport; import de.tum.cit.aet.artemis.programming.dto.ResultDTO; import de.tum.cit.aet.artemis.quiz.config.QuizView; import de.tum.cit.aet.artemis.quiz.domain.QuizExercise; @@ -152,13 +148,6 @@ public class Result extends DomainObject implements Comparable { @JsonIgnore private Instant lastModifiedDate; - // This attribute is required to forward the coverage file reports after creating the build result. This is required in order to - // delay referencing the corresponding test cases from the entries because the test cases are not saved in the database - // at this point of time but the required test case name would be lost, otherwise. - @Transient - @JsonIgnore - private Map> fileReportsByTestCaseName; - public ZonedDateTime getCompletionDate() { return completionDate; } @@ -500,14 +489,6 @@ public void setCodeIssueCount(int codeIssueCount) { this.codeIssueCount = Math.min(codeIssueCount, SIZE_OF_UNSIGNED_TINYINT); } - public Map> getCoverageFileReportsByTestCaseName() { - return fileReportsByTestCaseName; - } - - public void setCoverageFileReportsByTestCaseName(Map> fileReportsByTestCaseName) { - this.fileReportsByTestCaseName = fileReportsByTestCaseName; - } - /** * Checks the initialization status of the assessment note before returning. Only a single element is returned instead of the list, * because it is modelled that way on the client-side. Jackson therefore needs a single object for the (de-)serialization. diff --git a/src/main/java/de/tum/cit/aet/artemis/assessment/repository/LongFeedbackTextRepository.java b/src/main/java/de/tum/cit/aet/artemis/assessment/repository/LongFeedbackTextRepository.java index 4e0c502a75db..f3a8909a6978 100644 --- a/src/main/java/de/tum/cit/aet/artemis/assessment/repository/LongFeedbackTextRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/assessment/repository/LongFeedbackTextRepository.java @@ -52,6 +52,10 @@ public interface LongFeedbackTextRepository extends ArtemisJpaRepository feedbackIds); + @Modifying + @Transactional + void deleteByFeedbackId(final Long feedbackId); + default LongFeedbackText findByFeedbackIdWithFeedbackAndResultAndParticipationElseThrow(final Long feedbackId) { return getValueElseThrow(findWithFeedbackAndResultAndParticipationByFeedbackId(feedbackId), feedbackId); } diff --git a/src/main/java/de/tum/cit/aet/artemis/assessment/service/ResultService.java b/src/main/java/de/tum/cit/aet/artemis/assessment/service/ResultService.java index 1dce0090c001..7c91008842c4 100644 --- a/src/main/java/de/tum/cit/aet/artemis/assessment/service/ResultService.java +++ b/src/main/java/de/tum/cit/aet/artemis/assessment/service/ResultService.java @@ -67,16 +67,16 @@ import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise; import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseParticipation; import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseStudentParticipation; +import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseTask; import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseTestCase; import de.tum.cit.aet.artemis.programming.domain.build.BuildPlanType; -import de.tum.cit.aet.artemis.programming.domain.hestia.ProgrammingExerciseTask; import de.tum.cit.aet.artemis.programming.repository.BuildJobRepository; import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseRepository; import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseStudentParticipationRepository; import de.tum.cit.aet.artemis.programming.repository.SolutionProgrammingExerciseParticipationRepository; import de.tum.cit.aet.artemis.programming.repository.TemplateProgrammingExerciseParticipationRepository; import de.tum.cit.aet.artemis.programming.service.BuildLogEntryService; -import de.tum.cit.aet.artemis.programming.service.hestia.ProgrammingExerciseTaskService; +import de.tum.cit.aet.artemis.programming.service.ProgrammingExerciseTaskService; @Profile(PROFILE_CORE) @Service @@ -198,8 +198,8 @@ public Result createNewManualResult(Result result, boolean ratedResult) { return savedResult; } - public Result createNewRatedManualResult(Result result) { - return createNewManualResult(result, true); + public void createNewRatedManualResult(Result result) { + createNewManualResult(result, true); } /** @@ -522,23 +522,26 @@ private void handleFeedbackPersistence(Feedback feedback, Result result, Map continuousIntegrationService, ProgrammingExerciseGradingService programmingExerciseGradingService, - ResultService resultService, TestwiseCoverageService testwiseCoverageService, ProgrammingTriggerService programmingTriggerService, - ProgrammingMessagingService programmingMessagingService) { + ResultService resultService, ProgrammingTriggerService programmingTriggerService, ProgrammingMessagingService programmingMessagingService) { this.continuousIntegrationService = continuousIntegrationService; this.programmingExerciseGradingService = programmingExerciseGradingService; this.resultService = resultService; - this.testwiseCoverageService = testwiseCoverageService; this.programmingTriggerService = programmingTriggerService; this.programmingMessagingService = programmingMessagingService; } @@ -125,12 +120,6 @@ public ResponseEntity processNewProgrammingExerciseResult(@RequestHeader(" // This method will return without triggering the build if the submission is not of type TEST. var programmingSubmission = (ProgrammingSubmission) result.getSubmission(); triggerTemplateBuildIfTestCasesChanged(participation.getProgrammingExercise().getId(), programmingSubmission); - - // the test cases and the submission have been saved to the database previously, therefore we can add the reference to the coverage reports - if (Boolean.TRUE.equals(participation.getProgrammingExercise().getBuildConfig().isTestwiseCoverageEnabled()) && Boolean.TRUE.equals(result.isSuccessful())) { - testwiseCoverageService.createTestwiseCoverageReport(result.getCoverageFileReportsByTestCaseName(), participation.getProgrammingExercise(), - programmingSubmission); - } } programmingMessagingService.notifyUserAboutNewResult(result, participation); diff --git a/src/main/java/de/tum/cit/aet/artemis/atlas/service/LearningObjectImportService.java b/src/main/java/de/tum/cit/aet/artemis/atlas/service/LearningObjectImportService.java index d20c671193de..19f5081bb032 100644 --- a/src/main/java/de/tum/cit/aet/artemis/atlas/service/LearningObjectImportService.java +++ b/src/main/java/de/tum/cit/aet/artemis/atlas/service/LearningObjectImportService.java @@ -55,7 +55,7 @@ import de.tum.cit.aet.artemis.plagiarism.service.PlagiarismDetectionConfigHelper; import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise; import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseRepository; -import de.tum.cit.aet.artemis.programming.repository.hestia.ProgrammingExerciseTaskRepository; +import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseTaskRepository; import de.tum.cit.aet.artemis.programming.service.ProgrammingExerciseImportService; import de.tum.cit.aet.artemis.quiz.domain.QuizExercise; import de.tum.cit.aet.artemis.quiz.repository.QuizExerciseRepository; @@ -261,7 +261,6 @@ else if (foundByShortName.isPresent()) { private void clearProgrammingExerciseAttributes(ProgrammingExercise programmingExercise) { programmingExercise.setTasks(null); - programmingExercise.setExerciseHints(new HashSet<>()); programmingExercise.setTestCases(new HashSet<>()); programmingExercise.setStaticCodeAnalysisCategories(new HashSet<>()); programmingExercise.setTeams(new HashSet<>()); diff --git a/src/main/java/de/tum/cit/aet/artemis/buildagent/dto/BuildConfig.java b/src/main/java/de/tum/cit/aet/artemis/buildagent/dto/BuildConfig.java index 34d139aa6f19..4f8df8d2f49d 100644 --- a/src/main/java/de/tum/cit/aet/artemis/buildagent/dto/BuildConfig.java +++ b/src/main/java/de/tum/cit/aet/artemis/buildagent/dto/BuildConfig.java @@ -11,12 +11,12 @@ // NOTE: this data structure is used in shared code between core and build agent nodes. Changing it requires that the shared data structures in Hazelcast (or potentially Redis) // in the future are migrated or cleared. Changes should be communicated in release notes as potentially breaking changes. +// TODO: reduce the amount of parameters and combine some in smaller record DTOs @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_EMPTY) public record BuildConfig(String buildScript, String dockerImage, String commitHashToBuild, String assignmentCommitHash, String testCommitHash, String branch, - ProgrammingLanguage programmingLanguage, ProjectType projectType, boolean scaEnabled, boolean sequentialTestRunsEnabled, boolean testwiseCoverageEnabled, - List resultPaths, int timeoutSeconds, String assignmentCheckoutPath, String testCheckoutPath, String solutionCheckoutPath, DockerRunConfig dockerRunConfig) - implements Serializable { + ProgrammingLanguage programmingLanguage, ProjectType projectType, boolean scaEnabled, boolean sequentialTestRunsEnabled, List resultPaths, int timeoutSeconds, + String assignmentCheckoutPath, String testCheckoutPath, String solutionCheckoutPath, DockerRunConfig dockerRunConfig) implements Serializable { @Override public String dockerImage() { diff --git a/src/main/java/de/tum/cit/aet/artemis/buildagent/dto/BuildResult.java b/src/main/java/de/tum/cit/aet/artemis/buildagent/dto/BuildResult.java index 9d78a5fe4984..b9ab91a21301 100644 --- a/src/main/java/de/tum/cit/aet/artemis/buildagent/dto/BuildResult.java +++ b/src/main/java/de/tum/cit/aet/artemis/buildagent/dto/BuildResult.java @@ -3,20 +3,15 @@ import java.io.Serializable; import java.time.ZonedDateTime; import java.util.ArrayList; -import java.util.Collections; import java.util.List; - -import org.springframework.util.ObjectUtils; +import java.util.Objects; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import de.tum.cit.aet.artemis.programming.domain.build.BuildLogEntry; -import de.tum.cit.aet.artemis.programming.dto.AbstractBuildResultNotificationDTO; -import de.tum.cit.aet.artemis.programming.dto.BuildJobDTOInterface; +import de.tum.cit.aet.artemis.programming.dto.BuildResultNotification; import de.tum.cit.aet.artemis.programming.dto.StaticCodeAnalysisReportDTO; -import de.tum.cit.aet.artemis.programming.dto.TestCaseBaseDTO; -import de.tum.cit.aet.artemis.programming.service.ci.notification.dto.TestwiseCoverageReportDTO; /** * Represents all the information returned by the local CI system about a build. @@ -27,88 +22,30 @@ // in the future are migrated or cleared. Changes should be communicated in release notes as potentially breaking changes. @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_EMPTY) -// TODO: this should be a record in the future -public class BuildResult extends AbstractBuildResultNotificationDTO implements Serializable { - - private final String assignmentRepoBranchName; - - private final String assignmentRepoCommitHash; - - private final String testsRepoCommitHash; - - private final boolean isBuildSuccessful; - - private final ZonedDateTime buildRunDate; - - private final List jobs; - - private List buildLogEntries = new ArrayList<>(); - - private final List staticCodeAnalysisReports; - - private boolean hasLogs = false; - - public BuildResult(String assignmentRepoBranchName, String assignmentRepoCommitHash, String testsRepoCommitHash, boolean isBuildSuccessful, ZonedDateTime buildRunDate, - List jobs, List staticCodeAnalysisReports) { - this.assignmentRepoBranchName = assignmentRepoBranchName; - this.assignmentRepoCommitHash = assignmentRepoCommitHash; - this.testsRepoCommitHash = testsRepoCommitHash; - this.isBuildSuccessful = isBuildSuccessful; - this.buildRunDate = buildRunDate; - this.jobs = jobs; - this.staticCodeAnalysisReports = staticCodeAnalysisReports; - } - - public BuildResult(String assignmentRepoBranchName, String assignmentRepoCommitHash, String testsRepoCommitHash, boolean isBuildSuccessful) { - this.assignmentRepoBranchName = assignmentRepoBranchName; - this.assignmentRepoCommitHash = assignmentRepoCommitHash; - this.testsRepoCommitHash = testsRepoCommitHash; - this.isBuildSuccessful = isBuildSuccessful; - this.buildRunDate = ZonedDateTime.now(); - this.jobs = new ArrayList<>(); - this.staticCodeAnalysisReports = new ArrayList<>(); - } +public record BuildResult(String assignmentRepoBranchName, String assignmentRepoCommitHash, String testsRepoCommitHash, boolean isBuildSuccessful, ZonedDateTime buildRunDate, + List jobs, List buildLogEntries, List staticCodeAnalysisReports, boolean hasLogs) + implements BuildResultNotification, Serializable { - @Override - public ZonedDateTime getBuildRunDate() { - return buildRunDate; - } - - @Override - protected String getCommitHashFromAssignmentRepo() { - if (ObjectUtils.isEmpty(assignmentRepoCommitHash)) { - return null; - } - return assignmentRepoCommitHash; - } - - @Override - protected String getCommitHashFromTestsRepo() { - if (ObjectUtils.isEmpty(testsRepoCommitHash)) { - return null; - } - return testsRepoCommitHash; + public BuildResult { + buildRunDate = Objects.requireNonNullElse(buildRunDate, ZonedDateTime.now()); + jobs = Objects.requireNonNullElse(jobs, new ArrayList<>()); + staticCodeAnalysisReports = Objects.requireNonNullElse(staticCodeAnalysisReports, new ArrayList<>()); + buildLogEntries = Objects.requireNonNullElse(buildLogEntries, new ArrayList<>()); + hasLogs = !buildLogEntries.isEmpty(); } - @Override - public String getBranchNameFromAssignmentRepo() { - return assignmentRepoBranchName; - } - - @Override - public boolean isBuildSuccessful() { - return isBuildSuccessful; + public BuildResult(String branch, String assignmentRepoCommitHash, String testsRepoCommitHash, List buildLogs, boolean isBuildSuccessful) { + this(branch, assignmentRepoCommitHash, testsRepoCommitHash, isBuildSuccessful, null, null, buildLogs, null, buildLogs != null && !buildLogs.isEmpty()); } @Override - public Double getBuildScore() { + public Double buildScore() { // the real score is calculated in the grading service return 0D; } /** - * Local CI does not support checking for artifacts as of now. - * TODO LOCALVC_CI: Figure out in the build process whether an artifact was created, and return true here if yes. + * NOTE: Local CI does not support checking for artifacts as of now. * * @return will always return false because LocalCI does not support checking for artifacts. */ @@ -117,81 +54,9 @@ public boolean hasArtifact() { return false; } - @Override - public boolean hasLogs() { - return hasLogs; - } - @Override public List extractBuildLogs() { // convert the buildLogEntry DTOs to BuildLogEntry objects return buildLogEntries.stream().map(log -> new BuildLogEntry(log.time(), log.log())).toList(); } - - /** - * Setter for the buildLogEntries - * - * @param buildLogEntries the buildLogEntries to be set - */ - public void setBuildLogEntries(List buildLogEntries) { - this.buildLogEntries = buildLogEntries; - hasLogs = true; - } - - @Override - public List getBuildJobs() { - return jobs; - } - - @Override - public List getStaticCodeAnalysisReports() { - return staticCodeAnalysisReports; - } - - @Override - public List getTestwiseCoverageReports() { - // TODO LOCALVC_CI: Implement testwise coverage and return the reports here. - return Collections.emptyList(); - } - - /** - * Represents all the information returned by the local CI system about a job. - * In the current implementation of local CI, there is always one job per build. - * - * @param failedTests list of failed tests. - * @param successfulTests list of successful tests. - */ - @JsonInclude(JsonInclude.Include.NON_EMPTY) - public record LocalCIJobDTO(List failedTests, List successfulTests) implements BuildJobDTOInterface, Serializable { - - @Override - public List getFailedTests() { - return failedTests; - } - - @Override - public List getSuccessfulTests() { - return successfulTests; - } - } - - /** - * Represents the information about one test case, including the test case's name and potential error messages that indicate what went wrong. - * - * @param name name of the test case. - * @param errors list of error messages. - */ - @JsonInclude(JsonInclude.Include.NON_EMPTY) - public record LocalCITestJobDTO(String name, List errors) implements TestCaseBaseDTO, Serializable { - - @Override - public String getName() { - return name; - } - - @Override - public List getTestMessages() { - return errors; - } - } } diff --git a/src/main/java/de/tum/cit/aet/artemis/buildagent/dto/LocalCIJobDTO.java b/src/main/java/de/tum/cit/aet/artemis/buildagent/dto/LocalCIJobDTO.java new file mode 100644 index 000000000000..27410b0b60f7 --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/buildagent/dto/LocalCIJobDTO.java @@ -0,0 +1,26 @@ +package de.tum.cit.aet.artemis.buildagent.dto; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import com.fasterxml.jackson.annotation.JsonInclude; + +import de.tum.cit.aet.artemis.programming.dto.BuildJobInterface; + +/** + * Represents all the information returned by the local CI system about a job. + * In the current implementation of local CI, there is always one job per build. + * + * @param failedTests list of failed tests. + * @param successfulTests list of successful tests. + */ +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public record LocalCIJobDTO(List failedTests, List successfulTests) implements BuildJobInterface, Serializable { + + public LocalCIJobDTO { + failedTests = Objects.requireNonNullElse(failedTests, new ArrayList<>()); + successfulTests = Objects.requireNonNullElse(successfulTests, new ArrayList<>()); + } +} diff --git a/src/main/java/de/tum/cit/aet/artemis/buildagent/dto/LocalCITestJobDTO.java b/src/main/java/de/tum/cit/aet/artemis/buildagent/dto/LocalCITestJobDTO.java new file mode 100644 index 000000000000..3232501a8737 --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/buildagent/dto/LocalCITestJobDTO.java @@ -0,0 +1,18 @@ +package de.tum.cit.aet.artemis.buildagent.dto; + +import java.io.Serializable; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonInclude; + +import de.tum.cit.aet.artemis.programming.dto.TestCaseBase; + +/** + * Represents the information about one test case, including the test case's name and potential error messages that indicate what went wrong. + * + * @param name name of the test case. + * @param testMessages list of error messages. + */ +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public record LocalCITestJobDTO(String name, List testMessages) implements TestCaseBase, Serializable { +} diff --git a/src/main/java/de/tum/cit/aet/artemis/buildagent/service/BuildJobExecutionService.java b/src/main/java/de/tum/cit/aet/artemis/buildagent/service/BuildJobExecutionService.java index e7fd13c7b97e..c77f26b426d1 100644 --- a/src/main/java/de/tum/cit/aet/artemis/buildagent/service/BuildJobExecutionService.java +++ b/src/main/java/de/tum/cit/aet/artemis/buildagent/service/BuildJobExecutionService.java @@ -40,7 +40,10 @@ import com.github.dockerjava.api.exception.NotFoundException; import de.tum.cit.aet.artemis.buildagent.dto.BuildJobQueueItem; +import de.tum.cit.aet.artemis.buildagent.dto.BuildLogDTO; import de.tum.cit.aet.artemis.buildagent.dto.BuildResult; +import de.tum.cit.aet.artemis.buildagent.dto.LocalCIJobDTO; +import de.tum.cit.aet.artemis.buildagent.dto.LocalCITestJobDTO; import de.tum.cit.aet.artemis.core.exception.EntityNotFoundException; import de.tum.cit.aet.artemis.core.exception.GitException; import de.tum.cit.aet.artemis.core.exception.LocalCIException; @@ -327,9 +330,9 @@ private BuildResult runScriptAndParseResults(BuildJobQueueItem buildJob, String try { testResultsTarInputStream = buildJobContainerService.getArchiveFromContainer(containerId, LOCALCI_WORKING_DIRECTORY + LOCALCI_RESULTS_DIRECTORY); + var buildLogs = buildLogsMap.getAndTruncateBuildLogs(buildJob.id()); buildResult = parseTestResults(testResultsTarInputStream, buildJob.buildConfig().branch(), assignmentRepoCommitHash, testRepoCommitHash, buildCompletedDate, - buildJob.id()); - buildResult.setBuildLogEntries(buildLogsMap.getAndTruncateBuildLogs(buildJob.id())); + buildJob.id(), buildLogs); } catch (NotFoundException e) { msg = "Could not find test results in container " + containerName; @@ -389,10 +392,10 @@ private BuildResult runScriptAndParseResults(BuildJobQueueItem buildJob, String // --- Helper methods ---- private BuildResult parseTestResults(TarArchiveInputStream testResultsTarInputStream, String assignmentRepoBranchName, String assignmentRepoCommitHash, - String testsRepoCommitHash, ZonedDateTime buildCompletedDate, String buildJobId) throws IOException { + String testsRepoCommitHash, ZonedDateTime buildCompletedDate, String buildJobId, List buildLogs) throws IOException { - List failedTests = new ArrayList<>(); - List successfulTests = new ArrayList<>(); + List failedTests = new ArrayList<>(); + List successfulTests = new ArrayList<>(); List staticCodeAnalysisReports = new ArrayList<>(); TarArchiveEntry tarEntry; @@ -434,7 +437,7 @@ private BuildResult parseTestResults(TarArchiveInputStream testResultsTarInputSt } return constructBuildResult(failedTests, successfulTests, assignmentRepoBranchName, assignmentRepoCommitHash, testsRepoCommitHash, !failedTests.isEmpty(), - buildCompletedDate, staticCodeAnalysisReports); + buildCompletedDate, staticCodeAnalysisReports, buildLogs); } private boolean isValidTestResultFile(TarArchiveEntry tarArchiveEntry) { @@ -499,7 +502,7 @@ private String readTarEntryContent(TarArchiveInputStream tarArchiveInputStream) */ private BuildResult constructFailedBuildResult(String assignmentRepoBranchName, @Nullable String assignmentRepoCommitHash, @Nullable String testsRepoCommitHash, ZonedDateTime buildRunDate) { - return constructBuildResult(List.of(), List.of(), assignmentRepoBranchName, assignmentRepoCommitHash, testsRepoCommitHash, false, buildRunDate, List.of()); + return constructBuildResult(List.of(), List.of(), assignmentRepoBranchName, assignmentRepoCommitHash, testsRepoCommitHash, false, buildRunDate, List.of(), null); } /** @@ -513,14 +516,15 @@ private BuildResult constructFailedBuildResult(String assignmentRepoBranchName, * @param isBuildSuccessful Whether the build was successful or not. * @param buildRunDate The date when the build was completed. * @param staticCodeAnalysisReports The static code analysis reports + * @param buildLogs the build logs * @return a {@link BuildResult} */ - private BuildResult constructBuildResult(List failedTests, List successfulTests, String assignmentRepoBranchName, + private BuildResult constructBuildResult(List failedTests, List successfulTests, String assignmentRepoBranchName, String assignmentRepoCommitHash, String testsRepoCommitHash, boolean isBuildSuccessful, ZonedDateTime buildRunDate, - List staticCodeAnalysisReports) { - BuildResult.LocalCIJobDTO job = new BuildResult.LocalCIJobDTO(failedTests, successfulTests); - - return new BuildResult(assignmentRepoBranchName, assignmentRepoCommitHash, testsRepoCommitHash, isBuildSuccessful, buildRunDate, List.of(job), staticCodeAnalysisReports); + List staticCodeAnalysisReports, List buildLogs) { + LocalCIJobDTO job = new LocalCIJobDTO(failedTests, successfulTests); + return new BuildResult(assignmentRepoBranchName, assignmentRepoCommitHash, testsRepoCommitHash, isBuildSuccessful, buildRunDate, List.of(job), buildLogs, + staticCodeAnalysisReports, true); } private Path cloneRepository(VcsRepositoryUri repositoryUri, @Nullable String commitHash, boolean checkout, String buildJobId) { diff --git a/src/main/java/de/tum/cit/aet/artemis/buildagent/service/SharedQueueProcessingService.java b/src/main/java/de/tum/cit/aet/artemis/buildagent/service/SharedQueueProcessingService.java index cc9c853ec806..c833a52704ec 100644 --- a/src/main/java/de/tum/cit/aet/artemis/buildagent/service/SharedQueueProcessingService.java +++ b/src/main/java/de/tum/cit/aet/artemis/buildagent/service/SharedQueueProcessingService.java @@ -463,8 +463,7 @@ private void processBuild(BuildJobQueueItem buildJob) { buildLogsMap.removeBuildLogs(buildJob.id()); BuildResult failedResult = new BuildResult(buildJob.buildConfig().branch(), buildJob.buildConfig().assignmentCommitHash(), buildJob.buildConfig().testCommitHash(), - false); - failedResult.setBuildLogEntries(buildLogs); + buildLogs, false); ResultQueueItem resultQueueItem = new ResultQueueItem(failedResult, job, buildLogs, ex); if (processResults.get()) { diff --git a/src/main/java/de/tum/cit/aet/artemis/buildagent/service/TestResultXmlParser.java b/src/main/java/de/tum/cit/aet/artemis/buildagent/service/TestResultXmlParser.java index ef9190911239..51603dd8f20f 100644 --- a/src/main/java/de/tum/cit/aet/artemis/buildagent/service/TestResultXmlParser.java +++ b/src/main/java/de/tum/cit/aet/artemis/buildagent/service/TestResultXmlParser.java @@ -12,7 +12,7 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlText; -import de.tum.cit.aet.artemis.buildagent.dto.BuildResult; +import de.tum.cit.aet.artemis.buildagent.dto.LocalCITestJobDTO; public class TestResultXmlParser { @@ -75,8 +75,7 @@ public class TestResultXmlParser { * @param successfulTests A list of successful tests. This list will be populated by the method. * @throws IOException If an I/O error occurs while reading the test result file. */ - public static void processTestResultFile(String testResultFileString, List failedTests, List successfulTests) - throws IOException { + public static void processTestResultFile(String testResultFileString, List failedTests, List successfulTests) throws IOException { testResultFileString = testResultFileString.replaceAll(INVALID_XML_CHARS, ""); // The root element can be or @@ -105,7 +104,7 @@ public static void processTestResultFile(String testResultFileString, List failedTests, List successfulTests, TestSuite suite) { + private static void processTopLevelTestSuite(List failedTests, List successfulTests, TestSuite suite) { processTestSuiteWithNamePrefix(suite, failedTests, successfulTests, ""); } @@ -117,8 +116,7 @@ private static void processTopLevelTestSuite(List * @param successfulTests A list of successful tests. This list will be populated by the method. * @param outerNamePrefix The name prefix for the test suite, derived from its parent suites. */ - private static void processInnerTestSuite(TestSuite testSuite, List failedTests, List successfulTests, - String outerNamePrefix) { + private static void processInnerTestSuite(TestSuite testSuite, List failedTests, List successfulTests, String outerNamePrefix) { // namePrefix recursively accumulates all parent testsuite names seperated with dots String namePrefix; if (testSuite.name() != null) { @@ -140,18 +138,17 @@ private static void processInnerTestSuite(TestSuite testSuite, List failedTests, List successfulTests, - String namePrefix) { + private static void processTestSuiteWithNamePrefix(TestSuite testSuite, List failedTests, List successfulTests, String namePrefix) { for (TestCase testCase : testSuite.testCases()) { if (testCase.isSkipped()) { continue; } Failure failure = testCase.extractFailure(); if (failure != null) { - failedTests.add(new BuildResult.LocalCITestJobDTO(namePrefix + testCase.name(), List.of(failure.extractMessage()))); + failedTests.add(new LocalCITestJobDTO(namePrefix + testCase.name(), List.of(failure.extractMessage()))); } else { - successfulTests.add(new BuildResult.LocalCITestJobDTO(namePrefix + testCase.name(), List.of())); + successfulTests.add(new LocalCITestJobDTO(namePrefix + testCase.name(), List.of())); } } @@ -190,11 +187,11 @@ private boolean isSkipped() { private Failure extractFailure() { return failure != null ? failure : error; } - } - // Intentionally empty record to represent the skipped tag () - @JsonIgnoreProperties(ignoreUnknown = true) - record Skip() { + // Intentionally empty record to represent the skipped tag () + @JsonIgnoreProperties(ignoreUnknown = true) + record Skip() { + } } // Due to issues with Jackson this currently cannot be a record. diff --git a/src/main/java/de/tum/cit/aet/artemis/communication/domain/AnswerPost.java b/src/main/java/de/tum/cit/aet/artemis/communication/domain/AnswerPost.java index 9469d9e6d818..bcb3218eb621 100644 --- a/src/main/java/de/tum/cit/aet/artemis/communication/domain/AnswerPost.java +++ b/src/main/java/de/tum/cit/aet/artemis/communication/domain/AnswerPost.java @@ -14,7 +14,6 @@ import org.hibernate.annotations.Cache; import org.hibernate.annotations.CacheConcurrencyStrategy; -import org.hibernate.annotations.SQLRestriction; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; @@ -39,13 +38,6 @@ public class AnswerPost extends Posting { @OneToMany(mappedBy = "answerPost", cascade = CascadeType.REMOVE, orphanRemoval = true, fetch = FetchType.EAGER) private Set reactions = new HashSet<>(); - /*** - * The value 1 represents an answer post, given by the enum {{@link PostingType}} - */ - @OneToMany(mappedBy = "postId", cascade = CascadeType.REMOVE, orphanRemoval = true, fetch = FetchType.LAZY) - @SQLRestriction("post_type = 1") - private Set savedPosts = new HashSet<>(); - @ManyToOne @JsonIncludeProperties({ "id", "exercise", "lecture", "course", "courseWideContext", "conversation", "author" }) private Post post; @@ -90,11 +82,6 @@ public void setPost(Post post) { this.post = post; } - @JsonIgnore - public Set getSavedPosts() { - return savedPosts; - } - @JsonProperty("isSaved") public boolean getIsSaved() { return isSaved; diff --git a/src/main/java/de/tum/cit/aet/artemis/communication/domain/Post.java b/src/main/java/de/tum/cit/aet/artemis/communication/domain/Post.java index 3bb92cb6a540..f6511017f1c6 100644 --- a/src/main/java/de/tum/cit/aet/artemis/communication/domain/Post.java +++ b/src/main/java/de/tum/cit/aet/artemis/communication/domain/Post.java @@ -21,7 +21,6 @@ import org.hibernate.annotations.Cache; import org.hibernate.annotations.CacheConcurrencyStrategy; -import org.hibernate.annotations.SQLRestriction; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; @@ -57,13 +56,6 @@ public class Post extends Posting { @OneToMany(mappedBy = "post", cascade = CascadeType.REMOVE, orphanRemoval = true, fetch = FetchType.EAGER) private Set answers = new HashSet<>(); - /*** - * The value 0 represents a post, given by the enum {{@link PostingType}} - */ - @OneToMany(mappedBy = "postId", cascade = CascadeType.REMOVE, orphanRemoval = true, fetch = FetchType.LAZY) - @SQLRestriction("post_type = 0") - private Set savedPosts = new HashSet<>(); - @ElementCollection(fetch = FetchType.EAGER) @CollectionTable(name = "post_tag", joinColumns = @JoinColumn(name = "post_id")) @Column(name = "text") @@ -235,11 +227,6 @@ public void setVoteCount(Integer voteCount) { this.voteCount = voteCount != null ? voteCount : 0; } - @JsonIgnore - public Set getSavedPosts() { - return savedPosts; - } - @JsonProperty("isSaved") public boolean getIsSaved() { return isSaved; diff --git a/src/main/java/de/tum/cit/aet/artemis/communication/dto/ChannelDTO.java b/src/main/java/de/tum/cit/aet/artemis/communication/dto/ChannelDTO.java index 6e5738d3904a..6fd8d9c89fa2 100644 --- a/src/main/java/de/tum/cit/aet/artemis/communication/dto/ChannelDTO.java +++ b/src/main/java/de/tum/cit/aet/artemis/communication/dto/ChannelDTO.java @@ -197,6 +197,11 @@ else if (channel.getExam() != null) { } } + /** + * Converts the DTO to a channel entity + * + * @return the created channel entity based on the attributes in the DTO + */ public Channel toChannel() { Channel channel = new Channel(); channel.setName(this.name); diff --git a/src/main/java/de/tum/cit/aet/artemis/communication/repository/ConversationMessageRepository.java b/src/main/java/de/tum/cit/aet/artemis/communication/repository/ConversationMessageRepository.java index 16c5be3aedc8..2952c5213432 100644 --- a/src/main/java/de/tum/cit/aet/artemis/communication/repository/ConversationMessageRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/communication/repository/ConversationMessageRepository.java @@ -114,7 +114,6 @@ private PageImpl findPostsWithSpecification(Pageable pageable, Specificati LEFT JOIN FETCH p.conversation LEFT JOIN FETCH p.reactions LEFT JOIN FETCH p.tags - LEFT JOIN FETCH p.savedPosts LEFT JOIN FETCH p.answers a LEFT JOIN FETCH a.reactions LEFT JOIN FETCH a.post diff --git a/src/main/java/de/tum/cit/aet/artemis/communication/repository/PostRepository.java b/src/main/java/de/tum/cit/aet/artemis/communication/repository/PostRepository.java index 1ea95f1d6657..7427a9fc1ce6 100644 --- a/src/main/java/de/tum/cit/aet/artemis/communication/repository/PostRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/communication/repository/PostRepository.java @@ -48,7 +48,13 @@ default Post findPostOrMessagePostByIdElseThrow(Long postId) throws EntityNotFou List findAllByConversationId(Long conversationId); - List findAllByCourseId(Long courseId); + @Query(""" + SELECT p + FROM Post p + LEFT JOIN p.conversation conversation + WHERE conversation.course.id = :courseId + """) + List findAllByCourseId(@Param("courseId") Long courseId); List findByIdIn(List idList); } diff --git a/src/main/java/de/tum/cit/aet/artemis/communication/repository/SavedPostRepository.java b/src/main/java/de/tum/cit/aet/artemis/communication/repository/SavedPostRepository.java index e0a00a5896aa..f490650f4fc4 100644 --- a/src/main/java/de/tum/cit/aet/artemis/communication/repository/SavedPostRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/communication/repository/SavedPostRepository.java @@ -46,6 +46,16 @@ public interface SavedPostRepository extends ArtemisJpaRepository findSavedPostByPostIdAndPostType(Long postId, PostingType postType); + /*** * Query all post ids that a user has saved by a certain posting type. Cached by user id and post type. * diff --git a/src/main/java/de/tum/cit/aet/artemis/communication/service/AnswerMessageService.java b/src/main/java/de/tum/cit/aet/artemis/communication/service/AnswerMessageService.java index f7645c202f63..d195ef870d7a 100644 --- a/src/main/java/de/tum/cit/aet/artemis/communication/service/AnswerMessageService.java +++ b/src/main/java/de/tum/cit/aet/artemis/communication/service/AnswerMessageService.java @@ -12,6 +12,7 @@ import de.tum.cit.aet.artemis.communication.domain.AnswerPost; import de.tum.cit.aet.artemis.communication.domain.Post; +import de.tum.cit.aet.artemis.communication.domain.PostingType; import de.tum.cit.aet.artemis.communication.domain.conversation.Channel; import de.tum.cit.aet.artemis.communication.domain.conversation.Conversation; import de.tum.cit.aet.artemis.communication.domain.notification.SingleUserNotification; @@ -209,6 +210,10 @@ public void deleteAnswerMessageById(Long courseId, Long answerMessageId) { answerPostRepository.deleteById(answerMessageId); preparePostForBroadcast(updatedMessage); + // Delete all connected saved posts + var savedPosts = savedPostRepository.findSavedPostByPostIdAndPostType(answerMessageId, PostingType.ANSWER); + savedPostRepository.deleteAll(savedPosts); + broadcastForPost(new PostDTO(updatedMessage, MetisCrudAction.UPDATE), course.getId(), null, null); } diff --git a/src/main/java/de/tum/cit/aet/artemis/communication/service/ConversationMessagingService.java b/src/main/java/de/tum/cit/aet/artemis/communication/service/ConversationMessagingService.java index 06f9409bddc0..9f66ebb10a21 100644 --- a/src/main/java/de/tum/cit/aet/artemis/communication/service/ConversationMessagingService.java +++ b/src/main/java/de/tum/cit/aet/artemis/communication/service/ConversationMessagingService.java @@ -27,6 +27,7 @@ import de.tum.cit.aet.artemis.communication.domain.DisplayPriority; import de.tum.cit.aet.artemis.communication.domain.NotificationType; import de.tum.cit.aet.artemis.communication.domain.Post; +import de.tum.cit.aet.artemis.communication.domain.PostingType; import de.tum.cit.aet.artemis.communication.domain.conversation.Channel; import de.tum.cit.aet.artemis.communication.domain.conversation.Conversation; import de.tum.cit.aet.artemis.communication.domain.conversation.GroupChat; @@ -371,6 +372,10 @@ public void deleteMessageById(Long courseId, Long postId) { conversationParticipantRepository.decrementUnreadMessagesCountOfParticipants(conversation.getId(), user.getId()); conversation = conversationService.getConversationById(conversation.getId()); + // Delete all connected saved posts + var savedPosts = savedPostRepository.findSavedPostByPostIdAndPostType(postId, PostingType.POST); + savedPostRepository.deleteAll(savedPosts); + conversationService.notifyAllConversationMembersAboutUpdate(conversation); preparePostForBroadcast(post); broadcastForPost(new PostDTO(post, MetisCrudAction.DELETE), course.getId(), null, null); diff --git a/src/main/java/de/tum/cit/aet/artemis/communication/service/SavedPostScheduleService.java b/src/main/java/de/tum/cit/aet/artemis/communication/service/SavedPostScheduleService.java index 25e5922031f5..e2487b77e887 100644 --- a/src/main/java/de/tum/cit/aet/artemis/communication/service/SavedPostScheduleService.java +++ b/src/main/java/de/tum/cit/aet/artemis/communication/service/SavedPostScheduleService.java @@ -45,7 +45,7 @@ public void cleanupArchivedSavedPosts() { /** * Cleans up all saved posts where the post entity does not exist anymore */ - @Scheduled(cron = "0 0 0 * * *") + @Scheduled(cron = "0 5 0 * * *") public void cleanupOrphanedSavedPosts() { List orphanedPosts = savedPostRepository.findOrphanedPostReferences(); if (!orphanedPosts.isEmpty()) { diff --git a/src/main/java/de/tum/cit/aet/artemis/communication/service/conversation/ConversationService.java b/src/main/java/de/tum/cit/aet/artemis/communication/service/conversation/ConversationService.java index 6dd36e5e9bac..b9fbbf97715c 100644 --- a/src/main/java/de/tum/cit/aet/artemis/communication/service/conversation/ConversationService.java +++ b/src/main/java/de/tum/cit/aet/artemis/communication/service/conversation/ConversationService.java @@ -164,11 +164,11 @@ public Conversation loadConversationWithParticipantsIfGroupChat(Long conversatio } /** - * Gets the conversation in a course for which the user is a member + * Gets the conversations in a course for which the user is a member * * @param course the course * @param requestingUser the user for which the conversations are requested - * @return the conversation in the course for which the user is a member + * @return list of conversations in the course for which the user is a member */ public List getConversationsOfUser(Course course, User requestingUser) { var conversationsOfUser = new ArrayList(); diff --git a/src/main/java/de/tum/cit/aet/artemis/core/domain/User.java b/src/main/java/de/tum/cit/aet/artemis/core/domain/User.java index c435194cdf40..cebc42aafde9 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/domain/User.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/domain/User.java @@ -534,6 +534,10 @@ public void setIrisAcceptedTimestamp(@Nullable ZonedDateTime irisAccepted) { this.irisAccepted = irisAccepted; } + public boolean hasAcceptedIris() { + return irisAccepted != null; + } + /** * Checks if the user has accepted the Iris privacy policy. * If not, an {@link AccessForbiddenException} is thrown. diff --git a/src/main/java/de/tum/cit/aet/artemis/core/dto/CourseDeletionSummaryDTO.java b/src/main/java/de/tum/cit/aet/artemis/core/dto/CourseDeletionSummaryDTO.java index 2f3b8d51596c..8ee1f5982268 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/dto/CourseDeletionSummaryDTO.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/dto/CourseDeletionSummaryDTO.java @@ -3,5 +3,6 @@ import com.fasterxml.jackson.annotation.JsonInclude; @JsonInclude(JsonInclude.Include.NON_EMPTY) -public record CourseDeletionSummaryDTO(long numberOfBuilds, long numberOfCommunicationPosts, long numberOfAnswerPosts) { +public record CourseDeletionSummaryDTO(long numberOfBuilds, long numberOfCommunicationPosts, long numberOfAnswerPosts, long numberProgrammingExercises, long numberTextExercises, + long numberFileUploadExercises, long numberModelingExercises, long numberQuizExercises, long numberExams, long numberLectures) { } diff --git a/src/main/java/de/tum/cit/aet/artemis/core/repository/DataExportRepository.java b/src/main/java/de/tum/cit/aet/artemis/core/repository/DataExportRepository.java index fb87a2b0a8b0..33d9c104b5f6 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/repository/DataExportRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/repository/DataExportRepository.java @@ -37,7 +37,7 @@ public interface DataExportRepository extends ArtemisJpaRepository findAllToBeCreated(); /** - * Find all data exports that need to be deleted. This includes all data exports that have a creation date older than 7 days + * Find all data exports that need to be deleted. This includes all data exports that have a creation date older than 7 days and that have not been deleted or failed before * * @param thresholdDate the date to filter data exports, typically 7 days before today. * @return a set of data exports that need to be deleted @@ -47,6 +47,7 @@ public interface DataExportRepository extends ArtemisJpaRepository findAllToBeDeleted(@Param("thresholdDate") ZonedDateTime thresholdDate); diff --git a/src/main/java/de/tum/cit/aet/artemis/core/repository/UserRepository.java b/src/main/java/de/tum/cit/aet/artemis/core/repository/UserRepository.java index 0d3280cf5d96..6eb94330acff 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/repository/UserRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/repository/UserRepository.java @@ -920,7 +920,7 @@ private String getCurrentUserLogin() { } /** - * Get user with user groups and authorities with the username (i.e. user.getLogin() or principal.getName()) + * Get user with user groups and authorities with the username (i.e. user.getLogin() or principal.name()) * * @param username the username of the user who should be retrieved from the database * @return the user that belongs to the given principal with eagerly loaded groups and authorities diff --git a/src/main/java/de/tum/cit/aet/artemis/core/security/jwt/JWTFilter.java b/src/main/java/de/tum/cit/aet/artemis/core/security/jwt/JWTFilter.java index ff1ddcaaf3e3..d83fd44615b6 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/security/jwt/JWTFilter.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/security/jwt/JWTFilter.java @@ -62,7 +62,8 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo * @param tokenProvider the Artemis token provider used to generate and validate jwt's * @return the valid jwt or null if not found or invalid */ - public static @Nullable String extractValidJwt(HttpServletRequest httpServletRequest, TokenProvider tokenProvider) { + @Nullable + public static String extractValidJwt(HttpServletRequest httpServletRequest, TokenProvider tokenProvider) { var cookie = WebUtils.getCookie(httpServletRequest, JWT_COOKIE_NAME); var authHeader = httpServletRequest.getHeader(AUTHORIZATION_HEADER); @@ -76,8 +77,9 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo } String jwtToken = cookie != null ? getJwtFromCookie(cookie) : getJwtFromBearer(authHeader); + String source = cookie != null ? "cookie" : "bearer"; - if (!isJwtValid(tokenProvider, jwtToken)) { + if (!isJwtValid(tokenProvider, jwtToken, source)) { return null; } @@ -90,7 +92,8 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo * @param jwtCookie the cookie with Key "jwt" * @return the jwt or null if not found */ - private static @Nullable String getJwtFromCookie(@Nullable Cookie jwtCookie) { + @Nullable + private static String getJwtFromCookie(@Nullable Cookie jwtCookie) { if (jwtCookie == null) { return null; } @@ -103,7 +106,8 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo * @param jwtBearer the content of the Authorization header * @return the jwt or null if not found */ - private static @Nullable String getJwtFromBearer(@Nullable String jwtBearer) { + @Nullable + private static String getJwtFromBearer(@Nullable String jwtBearer) { if (!StringUtils.hasText(jwtBearer) || !jwtBearer.startsWith(BEARER_PREFIX)) { return null; } @@ -116,10 +120,11 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo * Checks if the jwt is valid * * @param tokenProvider the Artemis token provider used to generate and validate jwt's - * @param jwtToken the jwt + * @param jwtToken the jwt token which should be validated + * @param source the source of the jwt token * @return true if the jwt is valid, false if missing or invalid */ - private static boolean isJwtValid(TokenProvider tokenProvider, @Nullable String jwtToken) { - return StringUtils.hasText(jwtToken) && tokenProvider.validateTokenForAuthority(jwtToken); + private static boolean isJwtValid(TokenProvider tokenProvider, @Nullable String jwtToken, @Nullable String source) { + return StringUtils.hasText(jwtToken) && tokenProvider.validateTokenForAuthority(jwtToken, source); } } diff --git a/src/main/java/de/tum/cit/aet/artemis/core/security/jwt/TokenProvider.java b/src/main/java/de/tum/cit/aet/artemis/core/security/jwt/TokenProvider.java index de9a88d01dfa..98cb4560811d 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/security/jwt/TokenProvider.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/security/jwt/TokenProvider.java @@ -8,6 +8,7 @@ import java.util.List; import java.util.stream.Collectors; +import jakarta.annotation.Nullable; import jakarta.annotation.PostConstruct; import javax.crypto.SecretKey; @@ -125,19 +126,21 @@ public Authentication getAuthentication(String token) { * Validate an JWT Authorization Token * * @param authToken JWT Authorization Token + * @param source the source of the token * @return boolean indicating if token is valid */ - public boolean validateTokenForAuthority(String authToken) { - return validateJwsToken(authToken); + public boolean validateTokenForAuthority(String authToken, @Nullable String source) { + return validateJwsToken(authToken, source); } /** * Validate an JWT Authorization Token * * @param authToken JWT Authorization Token + * @param source the source of the token * @return boolean indicating if token is valid */ - private boolean validateJwsToken(String authToken) { + private boolean validateJwsToken(String authToken, @Nullable String source) { try { parseClaims(authToken); return true; @@ -161,7 +164,7 @@ private boolean validateJwsToken(String authToken) { catch (IllegalArgumentException e) { log.error("Token validation error {}", e.getMessage()); } - log.info("Invalid JWT token: {}", authToken); + log.info("Invalid JWT token: {} from source {}", authToken, source); return false; } @@ -169,13 +172,7 @@ private Claims parseClaims(String authToken) { return Jwts.parser().verifyWith(key).build().parseSignedClaims(authToken).getPayload(); } - public T getClaim(String token, String claimName, Class claimType) { - Claims claims = parseClaims(token); - return claims.get(claimName, claimType); - } - public Date getExpirationDate(String authToken) { return parseClaims(authToken).getExpiration(); } - } diff --git a/src/main/java/de/tum/cit/aet/artemis/core/service/CourseService.java b/src/main/java/de/tum/cit/aet/artemis/core/service/CourseService.java index 232e86070473..6122b92810c2 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/service/CourseService.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/service/CourseService.java @@ -98,6 +98,7 @@ import de.tum.cit.aet.artemis.exam.repository.ExerciseGroupRepository; import de.tum.cit.aet.artemis.exam.service.ExamDeletionService; import de.tum.cit.aet.artemis.exercise.domain.Exercise; +import de.tum.cit.aet.artemis.exercise.domain.ExerciseType; import de.tum.cit.aet.artemis.exercise.domain.IncludedInOverallScore; import de.tum.cit.aet.artemis.exercise.repository.ExerciseRepository; import de.tum.cit.aet.artemis.exercise.repository.StudentParticipationRepository; @@ -475,13 +476,26 @@ public Set findAllOnlineCoursesForPlatformForUser(String registrationId, * @return the course deletion summary */ public CourseDeletionSummaryDTO getDeletionSummary(Course course) { + Long courseId = course.getId(); + List programmingExerciseIds = course.getExercises().stream().map(Exercise::getId).toList(); long numberOfBuilds = buildJobRepository.countBuildJobsByExerciseIds(programmingExerciseIds); - List posts = postRepository.findAllByCourseId(course.getId()); + List posts = postRepository.findAllByCourseId(courseId); long numberOfCommunicationPosts = posts.size(); long numberOfAnswerPosts = answerPostRepository.countAnswerPostsByPostIdIn(posts.stream().map(Post::getId).toList()); - return new CourseDeletionSummaryDTO(numberOfBuilds, numberOfCommunicationPosts, numberOfAnswerPosts); + long numberLectures = lectureRepository.countByCourse_Id(courseId); + long numberExams = examRepository.countByCourse_Id(courseId); + + Map countByExerciseType = exerciseService.countByCourseIdGroupByType(courseId); + long numberProgrammingExercises = countByExerciseType.get(ExerciseType.PROGRAMMING); + long numberTextExercises = countByExerciseType.get(ExerciseType.TEXT); + long numberQuizExercises = countByExerciseType.get(ExerciseType.QUIZ); + long numberFileUploadExercises = countByExerciseType.get(ExerciseType.FILE_UPLOAD); + long numberModelingExercises = countByExerciseType.get(ExerciseType.MODELING); + + return new CourseDeletionSummaryDTO(numberOfBuilds, numberOfCommunicationPosts, numberOfAnswerPosts, numberProgrammingExercises, numberTextExercises, + numberFileUploadExercises, numberModelingExercises, numberQuizExercises, numberExams, numberLectures); } /** diff --git a/src/main/java/de/tum/cit/aet/artemis/core/service/TitleCacheEvictionService.java b/src/main/java/de/tum/cit/aet/artemis/core/service/TitleCacheEvictionService.java index 0ee7cceac695..67c43ec410c9 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/service/TitleCacheEvictionService.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/service/TitleCacheEvictionService.java @@ -29,7 +29,6 @@ import de.tum.cit.aet.artemis.exercise.domain.Exercise; import de.tum.cit.aet.artemis.lecture.domain.Lecture; import de.tum.cit.aet.artemis.modeling.domain.ApollonDiagram; -import de.tum.cit.aet.artemis.programming.domain.hestia.ExerciseHint; /** * Listens to Hibernate events and invalidates the cached titles of an entity if the title changed. @@ -121,15 +120,6 @@ else if (entity instanceof ApollonDiagram diagram) { else if (entity instanceof Exam exam) { evictIdFromCache("examTitle", exam.getId()); } - else if (entity instanceof ExerciseHint hint) { - if (hint.getExercise() == null) { - log.warn("Unable to clear title of exercise hint {}: Exercise not present", hint.getId()); - return; - } - - var combinedId = hint.getExercise().getId() + "-" + hint.getId(); - evictIdFromCache("exerciseHintTitle", combinedId); - } else if (entity instanceof ExerciseGroup exerciseGroup) { if (!Hibernate.isInitialized(exerciseGroup.getExercises())) { log.warn("Unable to clear title of exercises from exercise group {}: Exercises not initialized", exerciseGroup.getId()); diff --git a/src/main/java/de/tum/cit/aet/artemis/exam/repository/ExamRepository.java b/src/main/java/de/tum/cit/aet/artemis/exam/repository/ExamRepository.java index a884ed754e4a..48f17f7ae26d 100644 --- a/src/main/java/de/tum/cit/aet/artemis/exam/repository/ExamRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/exam/repository/ExamRepository.java @@ -385,6 +385,8 @@ SELECT COUNT(studentExam) """) long countGeneratedStudentExamsByExamWithoutTestRuns(@Param("examId") long examId); + long countByCourse_Id(Long courseId); + /** * Returns the title of the exam with the given id. * diff --git a/src/main/java/de/tum/cit/aet/artemis/exam/service/ExamImportService.java b/src/main/java/de/tum/cit/aet/artemis/exam/service/ExamImportService.java index 58fcf03cc97a..507a5c706975 100644 --- a/src/main/java/de/tum/cit/aet/artemis/exam/service/ExamImportService.java +++ b/src/main/java/de/tum/cit/aet/artemis/exam/service/ExamImportService.java @@ -33,7 +33,7 @@ import de.tum.cit.aet.artemis.modeling.service.ModelingExerciseImportService; import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise; import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseRepository; -import de.tum.cit.aet.artemis.programming.repository.hestia.ProgrammingExerciseTaskRepository; +import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseTaskRepository; import de.tum.cit.aet.artemis.programming.service.ProgrammingExerciseImportService; import de.tum.cit.aet.artemis.programming.service.ProgrammingExerciseService; import de.tum.cit.aet.artemis.quiz.domain.QuizExercise; diff --git a/src/main/java/de/tum/cit/aet/artemis/exercise/dto/ExerciseDetailsDTO.java b/src/main/java/de/tum/cit/aet/artemis/exercise/dto/ExerciseDetailsDTO.java index eaa46988ebda..c577b573ee6f 100644 --- a/src/main/java/de/tum/cit/aet/artemis/exercise/dto/ExerciseDetailsDTO.java +++ b/src/main/java/de/tum/cit/aet/artemis/exercise/dto/ExerciseDetailsDTO.java @@ -1,15 +1,11 @@ package de.tum.cit.aet.artemis.exercise.dto; -import java.util.Set; - import com.fasterxml.jackson.annotation.JsonInclude; import de.tum.cit.aet.artemis.exercise.domain.Exercise; import de.tum.cit.aet.artemis.iris.dto.IrisCombinedSettingsDTO; import de.tum.cit.aet.artemis.plagiarism.dto.PlagiarismCaseInfoDTO; -import de.tum.cit.aet.artemis.programming.domain.hestia.ExerciseHint; @JsonInclude(JsonInclude.Include.NON_EMPTY) -public record ExerciseDetailsDTO(Exercise exercise, IrisCombinedSettingsDTO irisSettings, PlagiarismCaseInfoDTO plagiarismCaseInfo, Set availableExerciseHints, - Set activatedExerciseHints) { +public record ExerciseDetailsDTO(Exercise exercise, IrisCombinedSettingsDTO irisSettings, PlagiarismCaseInfoDTO plagiarismCaseInfo) { } diff --git a/src/main/java/de/tum/cit/aet/artemis/exercise/dto/ExerciseTypeCountDTO.java b/src/main/java/de/tum/cit/aet/artemis/exercise/dto/ExerciseTypeCountDTO.java new file mode 100644 index 000000000000..094ec79cd85f --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/exercise/dto/ExerciseTypeCountDTO.java @@ -0,0 +1,13 @@ +package de.tum.cit.aet.artemis.exercise.dto; + +import java.util.Objects; + +import de.tum.cit.aet.artemis.exercise.domain.Exercise; +import de.tum.cit.aet.artemis.exercise.domain.ExerciseType; + +public record ExerciseTypeCountDTO(ExerciseType exerciseType, long count) { + + public ExerciseTypeCountDTO(Class exerciseType, Long count) { + this(ExerciseType.getExerciseTypeFromClass(exerciseType.asSubclass(Exercise.class)), Objects.requireNonNullElse(count, 0L)); + } +} diff --git a/src/main/java/de/tum/cit/aet/artemis/exercise/repository/ExerciseRepository.java b/src/main/java/de/tum/cit/aet/artemis/exercise/repository/ExerciseRepository.java index 60b85bc14f79..dbe00ff1083c 100644 --- a/src/main/java/de/tum/cit/aet/artemis/exercise/repository/ExerciseRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/exercise/repository/ExerciseRepository.java @@ -25,6 +25,7 @@ import de.tum.cit.aet.artemis.core.repository.base.ArtemisJpaRepository; import de.tum.cit.aet.artemis.exam.web.ExamResource; import de.tum.cit.aet.artemis.exercise.domain.Exercise; +import de.tum.cit.aet.artemis.exercise.dto.ExerciseTypeCountDTO; import de.tum.cit.aet.artemis.exercise.dto.ExerciseTypeMetricsEntry; /** @@ -627,4 +628,21 @@ SELECT count(e) > 0 AND e.exerciseGroup IS NOT NULL """) boolean isExamExercise(@Param("exerciseId") long exerciseId); + + /** + * Returns a mapping from exercise type to count for a given course id. Note that there are way fewer courses + * than exercise, so loading the course and joining the course is way faster than vice versa. + * + * @param courseId the courseId to get the exerciseType->count mapping for + * @return a list of mappings from exercise type to count + */ + @Query(""" + SELECT new de.tum.cit.aet.artemis.exercise.dto.ExerciseTypeCountDTO(TYPE(e), COUNT(e)) + FROM Course c + JOIN c.exercises e + WHERE c.id = :courseId + AND TYPE(e) IN (ModelingExercise, TextExercise, ProgrammingExercise, QuizExercise, FileUploadExercise) + GROUP BY TYPE(e) + """) + List countByCourseIdGroupedByType(@Param("courseId") long courseId); } diff --git a/src/main/java/de/tum/cit/aet/artemis/exercise/service/ExerciseService.java b/src/main/java/de/tum/cit/aet/artemis/exercise/service/ExerciseService.java index 73fce3b0f9f1..bcaa1fcbac35 100644 --- a/src/main/java/de/tum/cit/aet/artemis/exercise/service/ExerciseService.java +++ b/src/main/java/de/tum/cit/aet/artemis/exercise/service/ExerciseService.java @@ -6,6 +6,7 @@ import static java.time.ZonedDateTime.now; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; @@ -63,9 +64,11 @@ import de.tum.cit.aet.artemis.exam.service.ExamLiveEventsService; import de.tum.cit.aet.artemis.exercise.domain.Exercise; import de.tum.cit.aet.artemis.exercise.domain.ExerciseMode; +import de.tum.cit.aet.artemis.exercise.domain.ExerciseType; import de.tum.cit.aet.artemis.exercise.domain.Submission; import de.tum.cit.aet.artemis.exercise.domain.Team; import de.tum.cit.aet.artemis.exercise.domain.participation.StudentParticipation; +import de.tum.cit.aet.artemis.exercise.dto.ExerciseTypeCountDTO; import de.tum.cit.aet.artemis.exercise.repository.ExerciseRepository; import de.tum.cit.aet.artemis.exercise.repository.StudentParticipationRepository; import de.tum.cit.aet.artemis.exercise.repository.SubmissionRepository; @@ -829,4 +832,17 @@ public T saveWithCompetencyLinks(T exercise, Function public void reconnectCompetencyExerciseLinks(Exercise exercise) { exercise.getCompetencyLinks().forEach(link -> link.setExercise(exercise)); } + + /** + * Returns a map from exercise type to count of exercise given a course id. + * + * @param courseId the course id + * @return the mapping from exercise type to course type. If a course has no exercises for a specific type, the map contains an entry for that type with value 0. + */ + public Map countByCourseIdGroupByType(Long courseId) { + Map exerciseTypeCountMap = exerciseRepository.countByCourseIdGroupedByType(courseId).stream() + .collect(Collectors.toMap(ExerciseTypeCountDTO::exerciseType, ExerciseTypeCountDTO::count)); + + return Arrays.stream(ExerciseType.values()).collect(Collectors.toMap(type -> type, type -> exerciseTypeCountMap.getOrDefault(type, 0L))); + } } diff --git a/src/main/java/de/tum/cit/aet/artemis/exercise/service/ExerciseWithSubmissionsExportService.java b/src/main/java/de/tum/cit/aet/artemis/exercise/service/ExerciseWithSubmissionsExportService.java index 1ccd03d928fc..381591ecdb85 100644 --- a/src/main/java/de/tum/cit/aet/artemis/exercise/service/ExerciseWithSubmissionsExportService.java +++ b/src/main/java/de/tum/cit/aet/artemis/exercise/service/ExerciseWithSubmissionsExportService.java @@ -43,6 +43,8 @@ public abstract class ExerciseWithSubmissionsExportService { private static final String EMBEDDED_FILE_MARKDOWN_SYNTAX_REGEX = "\\[.*] *\\(/api/files/markdown/.*\\)"; + private static final String EMBEDDED_FILE_MARKDOWN_WITH_HOVERTEXT = "\\(/api/files/markdown/.* \".*\"\\)"; + private static final String EMBEDDED_FILE_HTML_SYNTAX_REGEX = ""; private static final String API_MARKDOWN_FILE_PATH = "/api/files/markdown/"; @@ -129,7 +131,15 @@ private void copyFilesEmbeddedWithMarkdownSyntax(Exercise exercise, List for (String embeddedFile : embeddedFilesWithMarkdownSyntax) { // avoid matching other closing ] or () in the squared brackets by getting the index of the last ] String lastPartOfMatchedString = embeddedFile.substring(embeddedFile.lastIndexOf("]") + 1); - String filePath = lastPartOfMatchedString.substring(lastPartOfMatchedString.indexOf("(") + 1, lastPartOfMatchedString.indexOf(")")); + String filePath; + + if (Pattern.compile(EMBEDDED_FILE_MARKDOWN_WITH_HOVERTEXT).matcher(lastPartOfMatchedString).matches()) { + filePath = lastPartOfMatchedString.substring(lastPartOfMatchedString.indexOf("(") + 1, lastPartOfMatchedString.indexOf(" ")); + } + else { + filePath = lastPartOfMatchedString.substring(lastPartOfMatchedString.indexOf("(") + 1, lastPartOfMatchedString.indexOf(")")); + } + constructFilenameAndCopyFile(exercise, exportErrors, embeddedFilesDir, filePath); } } diff --git a/src/main/java/de/tum/cit/aet/artemis/exercise/service/ParticipationService.java b/src/main/java/de/tum/cit/aet/artemis/exercise/service/ParticipationService.java index a8c4d3507813..3f3d8d43257e 100644 --- a/src/main/java/de/tum/cit/aet/artemis/exercise/service/ParticipationService.java +++ b/src/main/java/de/tum/cit/aet/artemis/exercise/service/ParticipationService.java @@ -50,7 +50,6 @@ import de.tum.cit.aet.artemis.programming.repository.BuildLogStatisticsEntryRepository; import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseRepository; import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseStudentParticipationRepository; -import de.tum.cit.aet.artemis.programming.repository.hestia.CoverageReportRepository; import de.tum.cit.aet.artemis.programming.service.BuildLogEntryService; import de.tum.cit.aet.artemis.programming.service.GitService; import de.tum.cit.aet.artemis.programming.service.ParticipationVcsAccessTokenService; @@ -93,8 +92,6 @@ public class ParticipationService { private final ResultService resultService; - private final CoverageReportRepository coverageReportRepository; - private final BuildLogStatisticsEntryRepository buildLogStatisticsEntryRepository; private final ParticipantScoreRepository participantScoreRepository; @@ -115,10 +112,9 @@ public ParticipationService(GitService gitService, Optional localCISharedBuildJobQueueService, ProfileService profileService, - ParticipationVcsAccessTokenService participationVCSAccessTokenService, CompetencyProgressApi competencyProgressApi) { + BuildLogStatisticsEntryRepository buildLogStatisticsEntryRepository, ParticipantScoreRepository participantScoreRepository, + StudentScoreRepository studentScoreRepository, TeamScoreRepository teamScoreRepository, Optional localCISharedBuildJobQueueService, + ProfileService profileService, ParticipationVcsAccessTokenService participationVCSAccessTokenService, CompetencyProgressApi competencyProgressApi) { this.gitService = gitService; this.continuousIntegrationService = continuousIntegrationService; this.versionControlService = versionControlService; @@ -131,7 +127,6 @@ public ParticipationService(GitService gitService, Optional irisSettingsService, - PlagiarismCaseService plagiarismCaseService, ExerciseHintService exerciseHintService) { + PlagiarismCaseService plagiarismCaseService) { this.exerciseService = exerciseService; this.exerciseDeletionService = exerciseDeletionService; this.participationService = participationService; @@ -128,7 +124,6 @@ public ExerciseResource(ExerciseService exerciseService, ExerciseDeletionService this.examAccessService = examAccessService; this.irisSettingsService = irisSettingsService; this.plagiarismCaseService = plagiarismCaseService; - this.exerciseHintService = exerciseHintService; } /** @@ -349,13 +344,7 @@ public ResponseEntity getExerciseDetails(@PathVariable Long .orElse(null); PlagiarismCaseInfoDTO plagiarismCaseInfo = plagiarismCaseService.getPlagiarismCaseInfoForExerciseAndUser(exercise.getId(), user.getId()).orElse(null); - if (exercise instanceof ProgrammingExercise programmingExercise) { - Set activatedExerciseHints = exerciseHintService.getActivatedExerciseHints(programmingExercise, user); - Set availableExerciseHints = exerciseHintService.getAvailableExerciseHints(programmingExercise, user); - return ResponseEntity.ok(new ExerciseDetailsDTO(exercise, irisSettings, plagiarismCaseInfo, availableExerciseHints, activatedExerciseHints)); - } - - return ResponseEntity.ok(new ExerciseDetailsDTO(exercise, irisSettings, plagiarismCaseInfo, null, null)); + return ResponseEntity.ok(new ExerciseDetailsDTO(exercise, irisSettings, plagiarismCaseInfo)); } /** diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/repository/IrisExerciseChatSessionRepository.java b/src/main/java/de/tum/cit/aet/artemis/iris/repository/IrisExerciseChatSessionRepository.java index 514be1f1d575..c6da437ac731 100644 --- a/src/main/java/de/tum/cit/aet/artemis/iris/repository/IrisExerciseChatSessionRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/iris/repository/IrisExerciseChatSessionRepository.java @@ -36,7 +36,6 @@ public interface IrisExerciseChatSessionRepository extends ArtemisJpaRepository< * @return A list of chat sessions sorted by creation date in descending order. */ @Query(""" - SELECT s FROM IrisExerciseChatSession s WHERE s.exercise.id = :exerciseId @@ -68,11 +67,9 @@ public interface IrisExerciseChatSessionRepository extends ArtemisJpaRepository< */ default List findLatestByExerciseIdAndUserIdWithMessages(Long exerciseId, Long userId, Pageable pageable) { List ids = findSessionsByExerciseIdAndUserId(exerciseId, userId, pageable).stream().map(DomainObject::getId).toList(); - if (ids.isEmpty()) { return Collections.emptyList(); } - return findSessionsWithMessagesByIdIn(ids); } diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/repository/IrisExerciseSettingsRepository.java b/src/main/java/de/tum/cit/aet/artemis/iris/repository/IrisExerciseSettingsRepository.java new file mode 100644 index 000000000000..74b3e5abf57c --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/iris/repository/IrisExerciseSettingsRepository.java @@ -0,0 +1,24 @@ +package de.tum.cit.aet.artemis.iris.repository; + +import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_IRIS; + +import org.springframework.context.annotation.Profile; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import de.tum.cit.aet.artemis.core.repository.base.ArtemisJpaRepository; +import de.tum.cit.aet.artemis.iris.domain.settings.IrisExerciseSettings; + +@Repository +@Profile(PROFILE_IRIS) +public interface IrisExerciseSettingsRepository extends ArtemisJpaRepository { + + @Query(""" + SELECT COUNT(s) > 0 + FROM IrisExerciseSettings s + WHERE s.exercise.id = :exerciseId + AND s.irisTextExerciseChatSettings.enabled = TRUE + """) + boolean isExerciseChatEnabled(@Param("exerciseId") long exerciseId); +} diff --git a/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisEventService.java b/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisEventService.java index 58f0b8a069b8..e2a8a785fc77 100644 --- a/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisEventService.java +++ b/src/main/java/de/tum/cit/aet/artemis/iris/service/pyris/PyrisEventService.java @@ -5,6 +5,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Profile; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import de.tum.cit.aet.artemis.iris.domain.session.IrisChatSession; @@ -41,17 +42,18 @@ public PyrisEventService(IrisCourseChatSessionService irisCourseChatSessionServi * * @see PyrisEvent */ + @Async public void trigger(PyrisEvent, ?> event) { log.debug("Starting to process event of type: {}", event.getClass().getSimpleName()); try { switch (event) { case CompetencyJolSetEvent competencyJolSetEvent -> { - log.info("Processing CompetencyJolSetEvent: {}", competencyJolSetEvent); + log.debug("Processing CompetencyJolSetEvent: {}", competencyJolSetEvent); competencyJolSetEvent.handleEvent(irisCourseChatSessionService); log.debug("Successfully processed CompetencyJolSetEvent"); } case NewResultEvent newResultEvent -> { - log.info("Processing NewResultEvent: {}", newResultEvent); + log.debug("Processing NewResultEvent: {}", newResultEvent); newResultEvent.handleEvent(irisExerciseChatSessionService); log.debug("Successfully processed NewResultEvent"); } diff --git a/src/main/java/de/tum/cit/aet/artemis/lecture/repository/LectureRepository.java b/src/main/java/de/tum/cit/aet/artemis/lecture/repository/LectureRepository.java index 33ce07e7b6fb..e441c1633b23 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lecture/repository/LectureRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/lecture/repository/LectureRepository.java @@ -216,4 +216,6 @@ default Lecture findByIdWithLectureUnitsAndSlidesAndAttachmentsElseThrow(long le GROUP BY l.course.id """) Set countVisibleLectures(@Param("courseIds") Set courseIds, @Param("now") ZonedDateTime now); + + long countByCourse_Id(Long courseId); } diff --git a/src/main/java/de/tum/cit/aet/artemis/lti/config/Lti13LaunchFilter.java b/src/main/java/de/tum/cit/aet/artemis/lti/config/Lti13LaunchFilter.java index 6929eee2d537..65624c32a3e5 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lti/config/Lti13LaunchFilter.java +++ b/src/main/java/de/tum/cit/aet/artemis/lti/config/Lti13LaunchFilter.java @@ -26,7 +26,7 @@ import de.tum.cit.aet.artemis.core.exception.LtiEmailAlreadyInUseException; import de.tum.cit.aet.artemis.core.security.SecurityUtils; import de.tum.cit.aet.artemis.lti.dto.Claims; -import de.tum.cit.aet.artemis.lti.dto.LtiAuthenticationResponse; +import de.tum.cit.aet.artemis.lti.dto.Lti13AuthenticationResponse; import de.tum.cit.aet.artemis.lti.service.Lti13Service; import uk.ac.ox.ctl.lti13.security.oauth2.client.lti.authentication.OidcAuthenticationToken; import uk.ac.ox.ctl.lti13.security.oauth2.client.lti.web.OAuth2LoginAuthenticationFilter; @@ -125,7 +125,7 @@ private void writeResponse(String targetLinkUri, OidcIdToken ltiIdToken, String log.info("User is authenticated, building LTI response"); lti13Service.buildLtiResponse(uriBuilder, response); } - LtiAuthenticationResponse jsonResponse = new LtiAuthenticationResponse(uriBuilder.build().toUriString(), ltiIdToken.getTokenValue(), clientRegistrationId); + Lti13AuthenticationResponse jsonResponse = new Lti13AuthenticationResponse(uriBuilder.build().toUriString(), ltiIdToken.getTokenValue(), clientRegistrationId); response.setContentType("application/json"); response.setCharacterEncoding("UTF-8"); diff --git a/src/main/java/de/tum/cit/aet/artemis/lti/dto/Claims.java b/src/main/java/de/tum/cit/aet/artemis/lti/dto/Claims.java index 41659ae1210b..38a1b7a24bba 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lti/dto/Claims.java +++ b/src/main/java/de/tum/cit/aet/artemis/lti/dto/Claims.java @@ -1,6 +1,6 @@ package de.tum.cit.aet.artemis.lti.dto; -public class Claims extends uk.ac.ox.ctl.lti13.lti.Claims { +public final class Claims extends uk.ac.ox.ctl.lti13.lti.Claims { /** * Constant for LTI Assignment and Grade Services (AGS) claim endpoint. diff --git a/src/main/java/de/tum/cit/aet/artemis/lti/dto/Lti13AgsClaim.java b/src/main/java/de/tum/cit/aet/artemis/lti/dto/Lti13AgsClaim.java index c3ba3dab86d0..0b417baa473e 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lti/dto/Lti13AgsClaim.java +++ b/src/main/java/de/tum/cit/aet/artemis/lti/dto/Lti13AgsClaim.java @@ -6,12 +6,14 @@ import org.springframework.security.oauth2.core.oidc.OidcIdToken; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; /** * A wrapper record for an LTI 1.3 Assignment and Grading Services Claim. We support the Score Publishing Service in order to transmit scores. */ +@JsonInclude(JsonInclude.Include.NON_EMPTY) public record Lti13AgsClaim(List scope, String lineItem) { /** diff --git a/src/main/java/de/tum/cit/aet/artemis/lti/dto/LtiAuthenticationResponse.java b/src/main/java/de/tum/cit/aet/artemis/lti/dto/Lti13AuthenticationResponse.java similarity index 58% rename from src/main/java/de/tum/cit/aet/artemis/lti/dto/LtiAuthenticationResponse.java rename to src/main/java/de/tum/cit/aet/artemis/lti/dto/Lti13AuthenticationResponse.java index 49470f2fdc5b..0a6de2a3c212 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lti/dto/LtiAuthenticationResponse.java +++ b/src/main/java/de/tum/cit/aet/artemis/lti/dto/Lti13AuthenticationResponse.java @@ -1,5 +1,7 @@ package de.tum.cit.aet.artemis.lti.dto; +import com.fasterxml.jackson.annotation.JsonInclude; + /** * Holds LTI authentication response details. * @@ -7,5 +9,6 @@ * @param ltiIdToken LTI service provided ID token. * @param clientRegistrationId Client's registration ID with LTI service. */ -public record LtiAuthenticationResponse(String targetLinkUri, String ltiIdToken, String clientRegistrationId) { +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public record Lti13AuthenticationResponse(String targetLinkUri, String ltiIdToken, String clientRegistrationId) { } diff --git a/src/main/java/de/tum/cit/aet/artemis/lti/dto/Lti13ClientRegistration.java b/src/main/java/de/tum/cit/aet/artemis/lti/dto/Lti13ClientRegistration.java index f61dc9af1367..ef651272c086 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lti/dto/Lti13ClientRegistration.java +++ b/src/main/java/de/tum/cit/aet/artemis/lti/dto/Lti13ClientRegistration.java @@ -1,276 +1,20 @@ package de.tum.cit.aet.artemis.lti.dto; -import java.util.Arrays; import java.util.List; -import org.springframework.security.oauth2.core.AuthorizationGrantType; - +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; -import de.tum.cit.aet.artemis.lti.config.CustomLti13Configurer; - /** * Represents the client registration details for an LTI 1.3 integration. * This class encapsulates information required for LTI 1.3 client registration, * including response types, grant types, redirect URIs, and tool configuration. */ -public class Lti13ClientRegistration { - - @JsonProperty("client_id") - private String clientId; - - @JsonProperty("response_types") - private List responseTypes; - - @JsonProperty("grant_types") - private List grantTypes; - - @JsonProperty("initiate_login_uri") - private String initiateLoginUri; - - @JsonProperty("redirect_uris") - private List redirectUris; - - @JsonProperty("client_name") - private String clientName; - - @JsonProperty("jwks_uri") - private String jwksUri; - - @JsonProperty("logo_uri") - private String logoUri; - - @JsonProperty("token_endpoint_auth_method") - private String tokenEndpointAuthMethod; - - private String scope; - - @JsonProperty("https://purl.imsglobal.org/spec/lti-tool-configuration") - private Lti13ToolConfiguration lti13ToolConfiguration; - - /** - * Default constructor necessary for conversion. - */ - public Lti13ClientRegistration() { // Necessary for conversion - } - - /** - * Constructs a new Lti13ClientRegistration with specified server URL and client registration ID. - * Initializes various properties such as grant types, response types, and tool configurations. - * - * @param serverUrl The server URL for LTI configuration. - * @param clientRegistrationId The client registration ID for LTI configuration. - */ - public Lti13ClientRegistration(String serverUrl, String clientRegistrationId) { - this.setGrantTypes(Arrays.asList(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue(), AuthorizationGrantType.AUTHORIZATION_CODE.getValue())); - this.setResponseTypes(List.of("id_token")); - this.setClientName("Artemis - " + serverUrl); - this.setTokenEndpointAuthMethod("private_key_jwt"); - this.setScope(String.join(" ", List.of(Scopes.AGS_SCORE, Scopes.AGS_RESULT))); - this.setRedirectUris(List.of(serverUrl + "/" + CustomLti13Configurer.LTI13_LOGIN_REDIRECT_PROXY_PATH)); - this.setInitiateLoginUri(serverUrl + "/" + CustomLti13Configurer.LTI13_LOGIN_INITIATION_PATH + "/" + clientRegistrationId); - this.setJwksUri(serverUrl + "/.well-known/jwks.json"); - this.setLogoUri(serverUrl + "/public/images/logo.png"); - - Lti13ToolConfiguration toolConfiguration = getLti13ToolConfiguration(serverUrl); - this.setLti13ToolConfiguration(toolConfiguration); - } - - private static Lti13ToolConfiguration getLti13ToolConfiguration(String serverUrl) { - Lti13ToolConfiguration toolConfiguration = new Lti13ToolConfiguration(); - - // Extracting the domain from the server URL - String[] urlParts = serverUrl.split("://"); - String domain = ""; - if (urlParts.length >= 1) { - domain = urlParts[1]; // Domain cannot include protocol - } - toolConfiguration.setDomain(domain); - toolConfiguration.setTargetLinkUri(serverUrl + "/courses"); - toolConfiguration.setDescription("Artemis: Interactive Learning with Individual Feedback"); - toolConfiguration.setClaims(Arrays.asList("iss", "email", "sub", "name", "given_name", "family_name")); - Message deepLinkingMessage = new Message(CustomLti13Configurer.LTI13_DEEPLINK_MESSAGE_REQUEST, serverUrl + "/" + CustomLti13Configurer.LTI13_DEEPLINK_REDIRECT_PATH); - toolConfiguration.setMessages(List.of(deepLinkingMessage)); - return toolConfiguration; - } - - public String getClientId() { - return clientId; - } - - public void setClientId(String clientId) { - this.clientId = clientId; - } - - public List getResponseTypes() { - return responseTypes; - } - - public void setResponseTypes(List responseTypes) { - this.responseTypes = responseTypes; - } - - public List getGrantTypes() { - return grantTypes; - } - - public void setGrantTypes(List grantTypes) { - this.grantTypes = grantTypes; - } - - public String getInitiateLoginUri() { - return initiateLoginUri; - } - - public void setInitiateLoginUri(String initiateLoginUri) { - this.initiateLoginUri = initiateLoginUri; - } - - public List getRedirectUris() { - return redirectUris; - } - - public void setRedirectUris(List redirectUris) { - this.redirectUris = redirectUris; - } - - public String getClientName() { - return clientName; - } - - public void setClientName(String clientName) { - this.clientName = clientName; - } - - public String getJwksUri() { - return jwksUri; - } - - public void setJwksUri(String jwksUri) { - this.jwksUri = jwksUri; - } - - public String getLogoUri() { - return logoUri; - } - - public void setLogoUri(String logoUri) { - this.logoUri = logoUri; - } - - public String getTokenEndpointAuthMethod() { - return tokenEndpointAuthMethod; - } - - public void setTokenEndpointAuthMethod(String tokenEndpointAuthMethod) { - this.tokenEndpointAuthMethod = tokenEndpointAuthMethod; - } - - public String getScope() { - return scope; - } - - public void setScope(String scope) { - this.scope = scope; - } - - public Lti13ToolConfiguration getLti13ToolConfiguration() { - return lti13ToolConfiguration; - } - - public void setLti13ToolConfiguration(Lti13ToolConfiguration lti13ToolConfiguration) { - this.lti13ToolConfiguration = lti13ToolConfiguration; - } - - /** - * Inner class representing the LTI 1.3 tool configuration. - */ - public static class Lti13ToolConfiguration { - - private String domain; - - @JsonProperty("target_link_uri") - private String targetLinkUri; - - private String description; - - private List messages; - - private List claims; - - public String getDomain() { - return domain; - } - - public void setDomain(String domain) { - this.domain = domain; - } - - public String getTargetLinkUri() { - return targetLinkUri; - } - - public void setTargetLinkUri(String targetLinkUri) { - this.targetLinkUri = targetLinkUri; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public List getMessages() { - return messages; - } - - public void setMessages(List messages) { - this.messages = messages; - } - - public List getClaims() { - return claims; - } - - public void setClaims(List claims) { - this.claims = claims; - } - } - - /** - * Inner class representing a message in LTI 1.3 tool configuration. - */ - public static class Message { - - private String type; - - @JsonProperty("target_link_uri") - private String targetLinkUri; - - public Message() {// Necessary for conversion - } - - public Message(String type, String targetLinkUri) { - this.type = type; - this.targetLinkUri = targetLinkUri; - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - public String getTargetLinkUri() { - return targetLinkUri; - } +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public record Lti13ClientRegistration(@JsonProperty("client_id") String clientId, @JsonProperty("response_types") List responseTypes, + @JsonProperty("grant_types") List grantTypes, @JsonProperty("initiate_login_uri") String initiateLoginUri, @JsonProperty("redirect_uris") List redirectUris, + @JsonProperty("client_name") String clientName, @JsonProperty("jwks_uri") String jwksUri, @JsonProperty("logo_uri") String logoUri, + @JsonProperty("token_endpoint_auth_method") String tokenEndpointAuthMethod, String scope, + @JsonProperty("https://purl.imsglobal.org/spec/lti-tool-configuration") Lti13ToolConfiguration lti13ToolConfiguration) { - public void setTargetLinkUri(String targetLinkUri) { - this.targetLinkUri = targetLinkUri; - } - } } diff --git a/src/main/java/de/tum/cit/aet/artemis/lti/dto/Lti13ClientRegistrationFactory.java b/src/main/java/de/tum/cit/aet/artemis/lti/dto/Lti13ClientRegistrationFactory.java new file mode 100644 index 000000000000..e7ffd354871e --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/lti/dto/Lti13ClientRegistrationFactory.java @@ -0,0 +1,48 @@ +package de.tum.cit.aet.artemis.lti.dto; + +import java.util.Arrays; +import java.util.List; + +import org.springframework.security.oauth2.core.AuthorizationGrantType; + +import de.tum.cit.aet.artemis.lti.config.CustomLti13Configurer; +import uk.ac.ox.ctl.lti13.security.oauth2.client.lti.web.LTIAuthorizationGrantType; + +public class Lti13ClientRegistrationFactory { + + /** + * Constructs a new Lti13ClientRegistration with specified server URL and client registration ID. + * Initializes various properties such as grant types, response types, and tool configurations. + * + * @param serverUrl The server URL for LTI configuration. + * @param clientRegistrationId The client registration ID for LTI configuration. + * @return A new Lti13ClientRegistration object with certain default values for Artemis + */ + public static Lti13ClientRegistration createRegistration(String serverUrl, String clientRegistrationId) { + var grantTypes = Arrays.asList(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue(), LTIAuthorizationGrantType.IMPLICIT.getValue()); + var responseTypes = List.of("id_token"); + var clientName = "Artemis - " + serverUrl; + var tokenEndpointAuthMethod = "private_key_jwt"; + var scope = String.join(" ", List.of(Scopes.AGS_SCORE, Scopes.AGS_RESULT)); + var redirectUris = List.of(serverUrl + "/" + CustomLti13Configurer.LTI13_LOGIN_REDIRECT_PROXY_PATH); + var initiateLoginUri = serverUrl + "/" + CustomLti13Configurer.LTI13_LOGIN_INITIATION_PATH + "/" + clientRegistrationId; + var jwksUri = serverUrl + "/.well-known/jwks.json"; + var logoUri = serverUrl + "/public/images/logo.png"; + + return new Lti13ClientRegistration(clientRegistrationId, responseTypes, grantTypes, initiateLoginUri, redirectUris, clientName, jwksUri, logoUri, tokenEndpointAuthMethod, + scope, createLti13ToolConfiguration(serverUrl)); + } + + private static Lti13ToolConfiguration createLti13ToolConfiguration(String serverUrl) { + + // Extracting the domain from the server URL + String[] urlParts = serverUrl.split("://"); + String domain = ""; + if (urlParts.length >= 1) { + domain = urlParts[1]; // Domain cannot include protocol + } + var claims = Arrays.asList("iss", "email", "sub", "name", "given_name", "family_name"); + var deepLinkingMessage = new Lti13Message(CustomLti13Configurer.LTI13_DEEPLINK_MESSAGE_REQUEST, serverUrl + "/" + CustomLti13Configurer.LTI13_DEEPLINK_REDIRECT_PATH); + return new Lti13ToolConfiguration(domain, serverUrl + "/courses", "Artemis: Interactive Learning with Individual Feedback", List.of(deepLinkingMessage), claims); + } +} diff --git a/src/main/java/de/tum/cit/aet/artemis/lti/dto/Lti13DeepLinkingResponse.java b/src/main/java/de/tum/cit/aet/artemis/lti/dto/Lti13DeepLinkingResponse.java index 8d9986d433c0..e622dee0ca5a 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lti/dto/Lti13DeepLinkingResponse.java +++ b/src/main/java/de/tum/cit/aet/artemis/lti/dto/Lti13DeepLinkingResponse.java @@ -7,6 +7,7 @@ import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames; import org.springframework.security.oauth2.core.oidc.OidcIdToken; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -34,6 +35,7 @@ * @param clientRegistrationId The client registration ID. * @param returnUrl The URL to return to after deep linking is completed. */ +@JsonInclude(JsonInclude.Include.NON_EMPTY) public record Lti13DeepLinkingResponse(@JsonProperty(IdTokenClaimNames.AUD) String aud, @JsonProperty(IdTokenClaimNames.ISS) String iss, @JsonProperty(IdTokenClaimNames.EXP) String exp, @JsonProperty(IdTokenClaimNames.IAT) String iat, @JsonProperty(IdTokenClaimNames.NONCE) String nonce, @JsonProperty(Claims.MSG) String message, @JsonProperty(Claims.LTI_DEPLOYMENT_ID) String deploymentId, @JsonProperty(Claims.MESSAGE_TYPE) String messageType, @@ -105,7 +107,6 @@ public Map getClaims() { claims.put(Claims.MESSAGE_TYPE, messageType()); claims.put(Claims.LTI_VERSION, ltiVersion()); claims.put(Claims.CONTENT_ITEMS, contentItems()); - return claims; } diff --git a/src/main/java/de/tum/cit/aet/artemis/lti/dto/Lti13LaunchRequest.java b/src/main/java/de/tum/cit/aet/artemis/lti/dto/Lti13LaunchRequest.java index 8576a7dff782..e33f0b62c007 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lti/dto/Lti13LaunchRequest.java +++ b/src/main/java/de/tum/cit/aet/artemis/lti/dto/Lti13LaunchRequest.java @@ -4,6 +4,7 @@ import org.springframework.security.oauth2.core.oidc.OidcIdToken; import org.springframework.util.Assert; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -19,6 +20,7 @@ * @param agsClaim An optional {@link Lti13AgsClaim} representing the Assignment and Grade Services claim, if present. * @param clientRegistrationId The client registration ID, identifying the tool registration with the platform. */ +@JsonInclude(JsonInclude.Include.NON_EMPTY) public record Lti13LaunchRequest(String iss, String sub, String deploymentId, String resourceLinkId, String targetLinkUri, Lti13AgsClaim agsClaim, String clientRegistrationId) { /** diff --git a/src/main/java/de/tum/cit/aet/artemis/lti/dto/Lti13Message.java b/src/main/java/de/tum/cit/aet/artemis/lti/dto/Lti13Message.java new file mode 100644 index 000000000000..43eb826f353e --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/lti/dto/Lti13Message.java @@ -0,0 +1,12 @@ +package de.tum.cit.aet.artemis.lti.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Inner class representing a message in LTI 1.3 tool configuration. + */ +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public record Lti13Message(String type, @JsonProperty("target_link_uri") String targetLinkUri) { + +} diff --git a/src/main/java/de/tum/cit/aet/artemis/lti/dto/Lti13PlatformConfiguration.java b/src/main/java/de/tum/cit/aet/artemis/lti/dto/Lti13PlatformConfiguration.java index e1a54be22f60..23e0aa772dfa 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lti/dto/Lti13PlatformConfiguration.java +++ b/src/main/java/de/tum/cit/aet/artemis/lti/dto/Lti13PlatformConfiguration.java @@ -1,5 +1,6 @@ package de.tum.cit.aet.artemis.lti.dto; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; /** @@ -11,6 +12,7 @@ * @param jwksUri The URI for the JSON Web Key Set (JWKS). * @param registrationEndpoint The endpoint URL for registration. */ +@JsonInclude(JsonInclude.Include.NON_EMPTY) public record Lti13PlatformConfiguration(@JsonProperty("issuer") String issuer, @JsonProperty("token_endpoint") String tokenEndpoint, @JsonProperty("authorization_endpoint") String authorizationEndpoint, @JsonProperty("jwks_uri") String jwksUri, @JsonProperty("registration_endpoint") String registrationEndpoint) { diff --git a/src/main/java/de/tum/cit/aet/artemis/lti/dto/Lti13ToolConfiguration.java b/src/main/java/de/tum/cit/aet/artemis/lti/dto/Lti13ToolConfiguration.java new file mode 100644 index 000000000000..99a4d8a907f3 --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/lti/dto/Lti13ToolConfiguration.java @@ -0,0 +1,14 @@ +package de.tum.cit.aet.artemis.lti.dto; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Inner class representing the LTI 1.3 tool configuration. + */ +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public record Lti13ToolConfiguration(String domain, @JsonProperty("target_link_uri") String targetLinkUri, String description, List messages, List claims) { + +} diff --git a/src/main/java/de/tum/cit/aet/artemis/lti/dto/Scopes.java b/src/main/java/de/tum/cit/aet/artemis/lti/dto/Scopes.java index ff34ce2fc460..735bbf7d15d3 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lti/dto/Scopes.java +++ b/src/main/java/de/tum/cit/aet/artemis/lti/dto/Scopes.java @@ -1,6 +1,6 @@ package de.tum.cit.aet.artemis.lti.dto; -public class Scopes { +public final class Scopes { public static final String AGS_SCORE = "https://purl.imsglobal.org/spec/lti-ags/scope/score"; diff --git a/src/main/java/de/tum/cit/aet/artemis/lti/service/LtiDynamicRegistrationService.java b/src/main/java/de/tum/cit/aet/artemis/lti/service/LtiDynamicRegistrationService.java index bb4ba0abf16e..636877628224 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lti/service/LtiDynamicRegistrationService.java +++ b/src/main/java/de/tum/cit/aet/artemis/lti/service/LtiDynamicRegistrationService.java @@ -20,6 +20,7 @@ import de.tum.cit.aet.artemis.core.exception.BadRequestAlertException; import de.tum.cit.aet.artemis.lti.domain.LtiPlatformConfiguration; import de.tum.cit.aet.artemis.lti.dto.Lti13ClientRegistration; +import de.tum.cit.aet.artemis.lti.dto.Lti13ClientRegistrationFactory; import de.tum.cit.aet.artemis.lti.dto.Lti13PlatformConfiguration; import de.tum.cit.aet.artemis.lti.repository.LtiPlatformConfigurationRepository; @@ -62,8 +63,7 @@ public void performDynamicRegistration(String openIdConfigurationUrl, String reg throw new BadRequestAlertException("Invalid platform configuration", "LTI", "invalidPlatformConfiguration"); } - Lti13ClientRegistration clientRegistrationResponse = postClientRegistrationToPlatform(platformConfiguration.registrationEndpoint(), clientRegistrationId, - registrationToken); + var clientRegistrationResponse = postClientRegistrationToPlatform(platformConfiguration.registrationEndpoint(), clientRegistrationId, registrationToken); LtiPlatformConfiguration ltiPlatformConfiguration = updateLtiPlatformConfiguration(clientRegistrationId, platformConfiguration, clientRegistrationResponse); ltiPlatformConfigurationRepository.save(ltiPlatformConfiguration); @@ -100,7 +100,7 @@ private Lti13ClientRegistration postClientRegistrationToPlatform(String registra headers.setBearerAuth(registrationToken); } - Lti13ClientRegistration lti13ClientRegistration = new Lti13ClientRegistration(artemisServerUrl, clientRegistrationId); + Lti13ClientRegistration lti13ClientRegistration = Lti13ClientRegistrationFactory.createRegistration(artemisServerUrl, clientRegistrationId); Lti13ClientRegistration registrationResponse = null; try { ResponseEntity response = restTemplate.postForEntity(registrationEndpoint, new HttpEntity<>(lti13ClientRegistration, headers), @@ -123,7 +123,7 @@ private LtiPlatformConfiguration updateLtiPlatformConfiguration(String registrat Lti13ClientRegistration clientRegistrationResponse) { LtiPlatformConfiguration ltiPlatformConfiguration = new LtiPlatformConfiguration(); ltiPlatformConfiguration.setRegistrationId(registrationId); - ltiPlatformConfiguration.setClientId(clientRegistrationResponse.getClientId()); + ltiPlatformConfiguration.setClientId(clientRegistrationResponse.clientId()); ltiPlatformConfiguration.setAuthorizationUri(platformConfiguration.authorizationEndpoint()); ltiPlatformConfiguration.setJwkSetUri(platformConfiguration.jwksUri()); ltiPlatformConfiguration.setTokenUri(platformConfiguration.tokenEndpoint()); diff --git a/src/main/java/de/tum/cit/aet/artemis/lti/service/LtiNewResultService.java b/src/main/java/de/tum/cit/aet/artemis/lti/service/LtiNewResultService.java index 7a64018a6969..a1dc241afa40 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lti/service/LtiNewResultService.java +++ b/src/main/java/de/tum/cit/aet/artemis/lti/service/LtiNewResultService.java @@ -3,6 +3,7 @@ import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_LTI; import org.springframework.context.annotation.Profile; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import de.tum.cit.aet.artemis.exercise.domain.participation.StudentParticipation; @@ -22,6 +23,7 @@ public LtiNewResultService(Lti13Service lti13Service) { * * @param participation The exercise participation for which a new build result is available */ + @Async public void onNewResult(StudentParticipation participation) { if (!participation.getExercise().getCourseViaExerciseGroupOrCourseMember().isOnlineCourse()) { return; diff --git a/src/main/java/de/tum/cit/aet/artemis/lti/service/OnlineCourseConfigurationService.java b/src/main/java/de/tum/cit/aet/artemis/lti/service/OnlineCourseConfigurationService.java index ffbf6db62f82..e70a464b8900 100644 --- a/src/main/java/de/tum/cit/aet/artemis/lti/service/OnlineCourseConfigurationService.java +++ b/src/main/java/de/tum/cit/aet/artemis/lti/service/OnlineCourseConfigurationService.java @@ -15,7 +15,6 @@ import org.springframework.context.annotation.Profile; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; -import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.stereotype.Service; import de.tum.cit.aet.artemis.core.domain.Course; @@ -24,6 +23,7 @@ import de.tum.cit.aet.artemis.lti.domain.LtiPlatformConfiguration; import de.tum.cit.aet.artemis.lti.domain.OnlineCourseConfiguration; import de.tum.cit.aet.artemis.lti.repository.LtiPlatformConfigurationRepository; +import uk.ac.ox.ctl.lti13.security.oauth2.client.lti.web.LTIAuthorizationGrantType; /** * Service Implementation for OnlineCourseConfiguration. @@ -104,7 +104,7 @@ public ClientRegistration getClientRegistration(LtiPlatformConfiguration ltiPlat .tokenUri(ltiPlatformConfiguration.getTokenUri()) // .redirectUri(artemisServerUrl + "/" + CustomLti13Configurer.LTI13_LOGIN_REDIRECT_PROXY_PATH) // .scope("openid") // - .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) // + .authorizationGrantType(LTIAuthorizationGrantType.IMPLICIT) // .build(); } catch (IllegalArgumentException e) { diff --git a/src/main/java/de/tum/cit/aet/artemis/plagiarism/service/ContinuousPlagiarismControlService.java b/src/main/java/de/tum/cit/aet/artemis/plagiarism/service/ContinuousPlagiarismControlService.java index 0111e5129295..3c2933133a9b 100644 --- a/src/main/java/de/tum/cit/aet/artemis/plagiarism/service/ContinuousPlagiarismControlService.java +++ b/src/main/java/de/tum/cit/aet/artemis/plagiarism/service/ContinuousPlagiarismControlService.java @@ -74,9 +74,8 @@ public ContinuousPlagiarismControlService(ExerciseRepository exerciseRepository, */ @Scheduled(cron = "${artemis.scheduling.continuous-plagiarism-control-trigger-time:0 0 5 * * *}") public void executeChecks() { - log.info("Starting continuous plagiarism control..."); - var exercises = exerciseRepository.findAllExercisesWithDueDateOnOrAfterYesterdayAndContinuousPlagiarismControlEnabledIsTrue(); + log.info("Starting scheduled continuous plagiarism control for {} exercises: {}", exercises.size(), exercises.stream().map(Exercise::getId).toList()); exercises.stream().filter(isBeforeDueDateOrAfterWithPostDueDateChecksEnabled).forEach(exercise -> { log.info("Started continuous plagiarism control for exercise: exerciseId={}, type={}.", exercise.getId(), exercise.getExerciseType()); final long startTime = System.nanoTime(); diff --git a/src/main/java/de/tum/cit/aet/artemis/plagiarism/service/PlagiarismDetectionService.java b/src/main/java/de/tum/cit/aet/artemis/plagiarism/service/PlagiarismDetectionService.java index e8d439d26c2a..55150026c442 100644 --- a/src/main/java/de/tum/cit/aet/artemis/plagiarism/service/PlagiarismDetectionService.java +++ b/src/main/java/de/tum/cit/aet/artemis/plagiarism/service/PlagiarismDetectionService.java @@ -12,7 +12,6 @@ import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; -import de.jplag.exceptions.ExitException; import de.tum.cit.aet.artemis.modeling.domain.ModelingExercise; import de.tum.cit.aet.artemis.plagiarism.domain.PlagiarismResult; import de.tum.cit.aet.artemis.plagiarism.domain.modeling.ModelingPlagiarismResult; @@ -60,7 +59,7 @@ public PlagiarismDetectionService(TextPlagiarismDetectionService textPlagiarismD * @param exercise exercise to check plagiarism * @return result of plagiarism checks */ - public TextPlagiarismResult checkTextExercise(TextExercise exercise) throws ExitException { + public TextPlagiarismResult checkTextExercise(TextExercise exercise) { var plagiarismResult = textPlagiarismDetectionService.checkPlagiarism(exercise, exercise.getPlagiarismDetectionConfig().getSimilarityThreshold(), exercise.getPlagiarismDetectionConfig().getMinimumScore(), exercise.getPlagiarismDetectionConfig().getMinimumSize()); log.info("Finished textPlagiarismDetectionService.checkPlagiarism for exercise {} with {} comparisons,", exercise.getId(), plagiarismResult.getComparisons().size()); @@ -75,8 +74,7 @@ public TextPlagiarismResult checkTextExercise(TextExercise exercise) throws Exit * @param exercise exercise to check plagiarism * @return result of plagiarism checks */ - public TextPlagiarismResult checkProgrammingExercise(ProgrammingExercise exercise) - throws ExitException, IOException, ProgrammingLanguageNotSupportedForPlagiarismDetectionException { + public TextPlagiarismResult checkProgrammingExercise(ProgrammingExercise exercise) throws IOException, ProgrammingLanguageNotSupportedForPlagiarismDetectionException { checkProgrammingLanguageSupport(exercise); var plagiarismResult = programmingPlagiarismDetectionService.checkPlagiarism(exercise.getId(), exercise.getPlagiarismDetectionConfig().getSimilarityThreshold(), diff --git a/src/main/java/de/tum/cit/aet/artemis/plagiarism/service/ProgrammingPlagiarismDetectionService.java b/src/main/java/de/tum/cit/aet/artemis/plagiarism/service/ProgrammingPlagiarismDetectionService.java index a1b16c64ade3..3152d0612fa5 100644 --- a/src/main/java/de/tum/cit/aet/artemis/plagiarism/service/ProgrammingPlagiarismDetectionService.java +++ b/src/main/java/de/tum/cit/aet/artemis/plagiarism/service/ProgrammingPlagiarismDetectionService.java @@ -32,7 +32,6 @@ import de.jplag.clustering.ClusteringOptions; import de.jplag.cpp.CPPLanguage; import de.jplag.csharp.CSharpLanguage; -import de.jplag.exceptions.ExitException; import de.jplag.golang.GoLanguage; import de.jplag.java.JavaLanguage; import de.jplag.javascript.JavaScriptLanguage; @@ -59,8 +58,8 @@ import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseRepository; import de.tum.cit.aet.artemis.programming.service.GitService; import de.tum.cit.aet.artemis.programming.service.ProgrammingExerciseExportService; +import de.tum.cit.aet.artemis.programming.service.ProgrammingExerciseGitDiffReportService; import de.tum.cit.aet.artemis.programming.service.UriService; -import de.tum.cit.aet.artemis.programming.service.hestia.ProgrammingExerciseGitDiffReportService; @Profile(PROFILE_CORE) @Service @@ -117,8 +116,7 @@ public ProgrammingPlagiarismDetectionService(FileService fileService, Programmin * @param minimumScore consider only submissions whose score is greater or equal to this value * @param minimumSize consider only submissions whose number of lines in diff to template is greater or equal to this value * @return the text plagiarism result container with up to 500 comparisons with the highest similarity values - * @throws ExitException is thrown if JPlag exits unexpectedly - * @throws IOException is thrown for file handling errors + * @throws IOException is thrown for file handling errors */ public TextPlagiarismResult checkPlagiarism(long programmingExerciseId, float similarityThreshold, int minimumScore, int minimumSize) throws IOException { long start = System.nanoTime(); diff --git a/src/main/java/de/tum/cit/aet/artemis/plagiarism/service/TextPlagiarismDetectionService.java b/src/main/java/de/tum/cit/aet/artemis/plagiarism/service/TextPlagiarismDetectionService.java index 6a5bbe7e945c..c69d979c3640 100644 --- a/src/main/java/de/tum/cit/aet/artemis/plagiarism/service/TextPlagiarismDetectionService.java +++ b/src/main/java/de/tum/cit/aet/artemis/plagiarism/service/TextPlagiarismDetectionService.java @@ -21,7 +21,6 @@ import de.jplag.JPlagResult; import de.jplag.Language; import de.jplag.clustering.ClusteringOptions; -import de.jplag.exceptions.ExitException; import de.jplag.options.JPlagOptions; import de.jplag.text.NaturalLanguage; import de.tum.cit.aet.artemis.core.exception.BadRequestAlertException; @@ -84,7 +83,6 @@ public List textSubmissionsForComparison(TextExercise exerciseWi * @param minimumScore consider only submissions whose score is greater or equal to this value * @param minimumSize consider only submissions whose size is greater or equal to this value * @return a zip file that can be returned to the client - * @throws ExitException is thrown if JPlag exits unexpectedly */ public TextPlagiarismResult checkPlagiarism(TextExercise textExercise, float similarityThreshold, int minimumScore, int minimumSize) { // Only one plagiarism check per course allowed diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/domain/ProgrammingExercise.java b/src/main/java/de/tum/cit/aet/artemis/programming/domain/ProgrammingExercise.java index c2a4666c7c1b..63d247ffdfc8 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/domain/ProgrammingExercise.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/domain/ProgrammingExercise.java @@ -49,8 +49,6 @@ import de.tum.cit.aet.artemis.exercise.domain.participation.StudentParticipation; import de.tum.cit.aet.artemis.exercise.service.ExerciseDateService; import de.tum.cit.aet.artemis.programming.domain.build.BuildPlanType; -import de.tum.cit.aet.artemis.programming.domain.hestia.ExerciseHint; -import de.tum.cit.aet.artemis.programming.domain.hestia.ProgrammingExerciseTask; import de.tum.cit.aet.artemis.programming.domain.submissionpolicy.SubmissionPolicy; import de.tum.cit.aet.artemis.programming.service.ProgrammingLanguageFeature; @@ -149,9 +147,6 @@ public String getType() { @Column(name = "project_type", table = "programming_exercise_details") private ProjectType projectType; - @OneToMany(mappedBy = "exercise", cascade = CascadeType.REMOVE, orphanRemoval = true, fetch = FetchType.LAZY) - private Set exerciseHints = new HashSet<>(); - @Column(name = "release_tests_with_example_solution", table = "programming_exercise_details", nullable = false) private boolean releaseTestsWithExampleSolution = false; @@ -815,21 +810,12 @@ public void validateSettingsForFeedbackRequest() { } } - public Set getExerciseHints() { - return exerciseHints; - } - - public void setExerciseHints(Set exerciseHints) { - this.exerciseHints = exerciseHints; - } - /** * {@inheritDoc} */ @Override public void disconnectRelatedEntities() { - Stream.of(exerciseHints, testCases, staticCodeAnalysisCategories).filter(Objects::nonNull).forEach(Collection::clear); - + Stream.of(testCases, staticCodeAnalysisCategories).filter(Objects::nonNull).forEach(Collection::clear); super.disconnectRelatedEntities(); } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/domain/ProgrammingExerciseBuildConfig.java b/src/main/java/de/tum/cit/aet/artemis/programming/domain/ProgrammingExerciseBuildConfig.java index d28e21bb3ad1..1a784bdf060d 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/domain/ProgrammingExerciseBuildConfig.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/domain/ProgrammingExerciseBuildConfig.java @@ -19,7 +19,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import de.tum.cit.aet.artemis.core.domain.DomainObject; -import de.tum.cit.aet.artemis.programming.service.aeolus.Windfile; +import de.tum.cit.aet.artemis.programming.dto.aeolus.Windfile; import de.tum.cit.aet.artemis.programming.service.vcs.AbstractVersionControlService; @Entity @@ -67,9 +67,6 @@ public class ProgrammingExerciseBuildConfig extends DomainObject { @JsonIgnoreProperties("buildConfig") private ProgrammingExercise programmingExercise; - @Column(name = "testwise_coverage_enabled") - private boolean testwiseCoverageEnabled; - @Nullable @Column(name = "theia_image") private String theiaImage; @@ -98,7 +95,6 @@ public ProgrammingExerciseBuildConfig(ProgrammingExerciseBuildConfig originalBui this.setDockerFlags(originalBuildConfig.getDockerFlags()); this.setSequentialTestRuns(originalBuildConfig.hasSequentialTestRuns()); this.setBuildScript(originalBuildConfig.getBuildScript()); - this.setTestwiseCoverageEnabled(originalBuildConfig.isTestwiseCoverageEnabled()); this.setTimeoutSeconds(originalBuildConfig.getTimeoutSeconds()); this.setTheiaImage(originalBuildConfig.getTheiaImage()); this.setAllowBranching(originalBuildConfig.isAllowBranching()); @@ -247,14 +243,6 @@ public void filterSensitiveInformation() { setBuildScript(null); } - public boolean isTestwiseCoverageEnabled() { - return testwiseCoverageEnabled; - } - - public void setTestwiseCoverageEnabled(boolean testwiseCoverageEnabled) { - this.testwiseCoverageEnabled = testwiseCoverageEnabled; - } - public ProgrammingExercise getProgrammingExercise() { return programmingExercise; } @@ -296,7 +284,7 @@ public void setSolutionCheckoutPath(String solutionCheckoutPath) { public String toString() { return "BuildJobConfig{" + "id=" + getId() + ", sequentialTestRuns=" + sequentialTestRuns + ", branch='" + branch + '\'' + ", buildPlanConfiguration='" + buildPlanConfiguration + '\'' + ", buildScript='" + buildScript + '\'' + ", checkoutSolutionRepository=" + checkoutSolutionRepository + ", checkoutPath='" - + testCheckoutPath + '\'' + ", timeoutSeconds=" + timeoutSeconds + ", dockerFlags='" + dockerFlags + '\'' + ", testwiseCoverageEnabled=" + testwiseCoverageEnabled - + ", theiaImage='" + theiaImage + '\'' + ", allowBranching=" + allowBranching + ", branchRegex='" + branchRegex + '\'' + '}'; + + testCheckoutPath + '\'' + ", timeoutSeconds=" + timeoutSeconds + ", dockerFlags='" + dockerFlags + '\'' + ", theiaImage='" + theiaImage + '\'' + + ", allowBranching=" + allowBranching + ", branchRegex='" + branchRegex + '\'' + '}'; } } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/domain/hestia/ProgrammingExerciseGitDiffEntry.java b/src/main/java/de/tum/cit/aet/artemis/programming/domain/ProgrammingExerciseGitDiffEntry.java similarity index 98% rename from src/main/java/de/tum/cit/aet/artemis/programming/domain/hestia/ProgrammingExerciseGitDiffEntry.java rename to src/main/java/de/tum/cit/aet/artemis/programming/domain/ProgrammingExerciseGitDiffEntry.java index 23095ebaea55..f1ff24c6c144 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/domain/hestia/ProgrammingExerciseGitDiffEntry.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/domain/ProgrammingExerciseGitDiffEntry.java @@ -1,4 +1,4 @@ -package de.tum.cit.aet.artemis.programming.domain.hestia; +package de.tum.cit.aet.artemis.programming.domain; import jakarta.persistence.Column; import jakarta.persistence.Entity; diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/domain/hestia/ProgrammingExerciseGitDiffReport.java b/src/main/java/de/tum/cit/aet/artemis/programming/domain/ProgrammingExerciseGitDiffReport.java similarity index 95% rename from src/main/java/de/tum/cit/aet/artemis/programming/domain/hestia/ProgrammingExerciseGitDiffReport.java rename to src/main/java/de/tum/cit/aet/artemis/programming/domain/ProgrammingExerciseGitDiffReport.java index b63d3fd713b1..d69f509ecf9e 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/domain/hestia/ProgrammingExerciseGitDiffReport.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/domain/ProgrammingExerciseGitDiffReport.java @@ -1,4 +1,4 @@ -package de.tum.cit.aet.artemis.programming.domain.hestia; +package de.tum.cit.aet.artemis.programming.domain; import java.util.Set; @@ -17,7 +17,6 @@ import com.fasterxml.jackson.annotation.JsonInclude; import de.tum.cit.aet.artemis.core.domain.DomainObject; -import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise; /** * A git-diff report representing a git-diff between the template and solution repositories of a ProgrammingExercise. diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/domain/hestia/ProgrammingExerciseTask.java b/src/main/java/de/tum/cit/aet/artemis/programming/domain/ProgrammingExerciseTask.java similarity index 73% rename from src/main/java/de/tum/cit/aet/artemis/programming/domain/hestia/ProgrammingExerciseTask.java rename to src/main/java/de/tum/cit/aet/artemis/programming/domain/ProgrammingExerciseTask.java index 4a7a26ddee49..9b5c0d95b29d 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/domain/hestia/ProgrammingExerciseTask.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/domain/ProgrammingExerciseTask.java @@ -1,9 +1,8 @@ -package de.tum.cit.aet.artemis.programming.domain.hestia; +package de.tum.cit.aet.artemis.programming.domain; import java.util.HashSet; import java.util.Set; -import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; @@ -11,7 +10,6 @@ import jakarta.persistence.JoinTable; import jakarta.persistence.ManyToMany; import jakarta.persistence.ManyToOne; -import jakarta.persistence.OneToMany; import jakarta.persistence.Table; import org.hibernate.annotations.Cache; @@ -22,8 +20,6 @@ import com.fasterxml.jackson.annotation.JsonInclude; import de.tum.cit.aet.artemis.core.domain.DomainObject; -import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise; -import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseTestCase; /** * A ProgrammingExerciseTask @@ -37,11 +33,6 @@ public class ProgrammingExerciseTask extends DomainObject { @Column(name = "task_name") private String taskName; - // No orphanRemoval here, as there should only be one parent-child relationship (which is ProgrammingExercise -> ExerciseHint) - @OneToMany(mappedBy = "task", cascade = CascadeType.REMOVE, fetch = FetchType.LAZY) - @JsonIgnoreProperties("task") - private Set exerciseHints = new HashSet<>(); - @ManyToMany(fetch = FetchType.LAZY) @JoinTable(name = "programming_exercise_task_test_case", joinColumns = @JoinColumn(name = "task_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "test_case_id", referencedColumnName = "id")) @JsonIgnoreProperties(value = { "tasks", "exercise" }, allowSetters = true) @@ -59,14 +50,6 @@ public void setTaskName(String taskName) { this.taskName = taskName; } - public Set getExerciseHints() { - return exerciseHints; - } - - public void setExerciseHints(Set exerciseHints) { - this.exerciseHints = exerciseHints; - } - public Set getTestCases() { return this.testCases; } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/domain/ProgrammingExerciseTestCase.java b/src/main/java/de/tum/cit/aet/artemis/programming/domain/ProgrammingExerciseTestCase.java index dae43fce9dd9..3da8bde413d7 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/domain/ProgrammingExerciseTestCase.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/domain/ProgrammingExerciseTestCase.java @@ -1,11 +1,10 @@ package de.tum.cit.aet.artemis.programming.domain; -import static de.tum.cit.aet.artemis.programming.domain.hestia.ProgrammingExerciseTestCaseType.DEFAULT; +import static de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseTestCaseType.DEFAULT; import java.util.HashSet; import java.util.Set; -import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; @@ -15,7 +14,6 @@ import jakarta.persistence.JoinTable; import jakarta.persistence.ManyToMany; import jakarta.persistence.ManyToOne; -import jakarta.persistence.OneToMany; import jakarta.persistence.Table; import jakarta.validation.constraints.NotNull; @@ -31,10 +29,6 @@ import de.tum.cit.aet.artemis.assessment.domain.Visibility; import de.tum.cit.aet.artemis.core.domain.DomainObject; import de.tum.cit.aet.artemis.exercise.domain.Exercise; -import de.tum.cit.aet.artemis.programming.domain.hestia.ProgrammingExerciseSolutionEntry; -import de.tum.cit.aet.artemis.programming.domain.hestia.ProgrammingExerciseTask; -import de.tum.cit.aet.artemis.programming.domain.hestia.ProgrammingExerciseTestCaseType; -import de.tum.cit.aet.artemis.programming.domain.hestia.TestwiseCoverageReportEntry; /** * A ProgrammingExerciseTestCase. @@ -69,10 +63,6 @@ public class ProgrammingExerciseTestCase extends DomainObject { @JsonIgnoreProperties({ "testCases", "exercise" }) private Set tasks = new HashSet<>(); - @OneToMany(mappedBy = "testCase", cascade = CascadeType.REMOVE, orphanRemoval = true, fetch = FetchType.LAZY) - @JsonIgnoreProperties(value = "testCase", allowSetters = true) - private Set solutionEntries = new HashSet<>(); - @ManyToOne(fetch = FetchType.LAZY) @JsonIgnoreProperties("testCases") private ProgrammingExercise exercise; @@ -81,10 +71,6 @@ public class ProgrammingExerciseTestCase extends DomainObject { @Column(name = "test_case_type", nullable = false) private ProgrammingExerciseTestCaseType type = DEFAULT; // default value - @OneToMany(mappedBy = "testCase", fetch = FetchType.LAZY) - @JsonIgnoreProperties("testCase") - private Set coverageEntries; - public ProgrammingExerciseTestCase id(Long id) { setId(id); return this; @@ -152,14 +138,6 @@ public void setTasks(Set tasks) { this.tasks = tasks; } - public Set getSolutionEntries() { - return solutionEntries; - } - - public void setSolutionEntries(Set solutionEntries) { - this.solutionEntries = solutionEntries; - } - public Boolean isActive() { return active; } @@ -217,14 +195,6 @@ public void setType(ProgrammingExerciseTestCaseType programmingExerciseTestCaseT this.type = programmingExerciseTestCaseType; } - public Set getCoverageEntries() { - return coverageEntries; - } - - public void setCoverageEntries(Set coverageEntries) { - this.coverageEntries = coverageEntries; - } - /** * Needs to be checked and updated if there is a new class attribute. Creates a clone with all attributes set to the value of the object, including the id. * diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/domain/hestia/ProgrammingExerciseTestCaseType.java b/src/main/java/de/tum/cit/aet/artemis/programming/domain/ProgrammingExerciseTestCaseType.java similarity index 77% rename from src/main/java/de/tum/cit/aet/artemis/programming/domain/hestia/ProgrammingExerciseTestCaseType.java rename to src/main/java/de/tum/cit/aet/artemis/programming/domain/ProgrammingExerciseTestCaseType.java index 047a3ca73eda..3996ca815ec5 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/domain/hestia/ProgrammingExerciseTestCaseType.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/domain/ProgrammingExerciseTestCaseType.java @@ -1,4 +1,4 @@ -package de.tum.cit.aet.artemis.programming.domain.hestia; +package de.tum.cit.aet.artemis.programming.domain; /** * Used to define the type of a ProgrammingExerciseTestCase. @@ -14,7 +14,7 @@ public enum ProgrammingExerciseTestCaseType { */ BEHAVIORAL, /** - * Type for all programming exercises not supported by Hestia + * All other types */ DEFAULT, } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/domain/hestia/CodeHint.java b/src/main/java/de/tum/cit/aet/artemis/programming/domain/hestia/CodeHint.java deleted file mode 100644 index a43ba0ac9c3f..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/domain/hestia/CodeHint.java +++ /dev/null @@ -1,69 +0,0 @@ -package de.tum.cit.aet.artemis.programming.domain.hestia; - -import java.util.HashSet; -import java.util.Set; - -import jakarta.persistence.DiscriminatorValue; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.OneToMany; -import jakarta.persistence.PreRemove; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; - -/** - * A CodeHint. - */ -@Entity -@DiscriminatorValue("C") -@JsonInclude(JsonInclude.Include.NON_EMPTY) -public class CodeHint extends ExerciseHint { - - // No CascadeType.REMOVE here, as we want to retain the solution entries when a code hint is deleted - @OneToMany(mappedBy = "codeHint", fetch = FetchType.LAZY) - @JsonIgnoreProperties(value = { "codeHint" }, allowSetters = true) - private Set solutionEntries = new HashSet<>(); - - public Set getSolutionEntries() { - return this.solutionEntries; - } - - public void setSolutionEntries(Set solutionEntries) { - this.solutionEntries = solutionEntries; - } - - /** - * This method ensures that all solution entry references are removed before deleting a CodeHint - */ - @PreRemove - public void preRemove() { - solutionEntries.forEach(solutionEntry -> solutionEntry.setCodeHint(null)); - } - - @Override - public void removeContent() { - super.removeContent(); - setSolutionEntries(new HashSet<>()); - } - - @Override - public String toString() { - return "CodeHint{" + "id=" + getId() + ", title='" + getTitle() + "}"; - } - - /** - * Creates a copy of this hint including basic attributes, but excluding attributes referencing other models - * - * @return The copied hint - */ - @Override - public CodeHint createCopy() { - CodeHint copiedHint = new CodeHint(); - - copiedHint.setDescription(this.getDescription()); - copiedHint.setContent(this.getContent()); - copiedHint.setTitle(this.getTitle()); - return copiedHint; - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/domain/hestia/CoverageFileReport.java b/src/main/java/de/tum/cit/aet/artemis/programming/domain/hestia/CoverageFileReport.java deleted file mode 100644 index 518c4bad4550..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/domain/hestia/CoverageFileReport.java +++ /dev/null @@ -1,86 +0,0 @@ -package de.tum.cit.aet.artemis.programming.domain.hestia; - -import java.util.Set; - -import jakarta.persistence.CascadeType; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.OneToMany; -import jakarta.persistence.Table; - -import org.hibernate.annotations.Cache; -import org.hibernate.annotations.CacheConcurrencyStrategy; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; - -import de.tum.cit.aet.artemis.core.domain.DomainObject; - -@Entity -@Table(name = "coverage_file_report") -@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) -@JsonInclude(JsonInclude.Include.NON_EMPTY) -public class CoverageFileReport extends DomainObject { - - @ManyToOne - @JsonIgnoreProperties("fileReports") - private CoverageReport fullReport; - - @Column(name = "file_path") - private String filePath; - - // The number of lines in the whole file - @Column(name = "line_count") - private Integer lineCount; - - // The number of lines that are covered by test cases. The number refers to the unique count, meaning the same line - // that is covered in multiple entries will only count as one line - @Column(name = "covered_line_count") - private Integer coveredLineCount; - - @OneToMany(mappedBy = "fileReport", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) - @JsonIgnoreProperties(value = { "fileReport" }, allowSetters = true) - private Set testwiseCoverageEntries; - - public CoverageReport getFullReport() { - return fullReport; - } - - public void setFullReport(CoverageReport fullReport) { - this.fullReport = fullReport; - } - - public String getFilePath() { - return filePath; - } - - public void setFilePath(String filePath) { - this.filePath = filePath; - } - - public Integer getLineCount() { - return lineCount; - } - - public void setLineCount(Integer lineCount) { - this.lineCount = lineCount; - } - - public Integer getCoveredLineCount() { - return coveredLineCount; - } - - public void setCoveredLineCount(Integer coveredLineCount) { - this.coveredLineCount = coveredLineCount; - } - - public Set getTestwiseCoverageEntries() { - return testwiseCoverageEntries; - } - - public void setTestwiseCoverageEntries(Set testwiseCoverageEntries) { - this.testwiseCoverageEntries = testwiseCoverageEntries; - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/domain/hestia/CoverageReport.java b/src/main/java/de/tum/cit/aet/artemis/programming/domain/hestia/CoverageReport.java deleted file mode 100644 index 9349483cb4e6..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/domain/hestia/CoverageReport.java +++ /dev/null @@ -1,75 +0,0 @@ -package de.tum.cit.aet.artemis.programming.domain.hestia; - -import java.util.Set; - -import jakarta.persistence.CascadeType; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.OneToMany; -import jakarta.persistence.OneToOne; -import jakarta.persistence.Table; - -import org.hibernate.annotations.Cache; -import org.hibernate.annotations.CacheConcurrencyStrategy; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; - -import de.tum.cit.aet.artemis.core.domain.DomainObject; -import de.tum.cit.aet.artemis.programming.domain.ProgrammingSubmission; - -/** - * A testwise coverage report representing the executed code by file path of a single ProgrammingExerciseTestCase. - * The entries contain the information about executed code by the start line and the length (i.e. number of lines) of - * a consecutively executed block. - */ -@Entity -@Table(name = "coverage_report") -@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) -@JsonInclude(JsonInclude.Include.NON_EMPTY) -public class CoverageReport extends DomainObject { - - // The ProgrammingSubmission to which this CoverageReport is related. This ProgrammingSubmission is always related - // to a SolutionProgrammingExerciseParticipation because the report will only be generated for solution participations. - @OneToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "submission_id", referencedColumnName = "id") - @JsonIgnore - private ProgrammingSubmission submission; - - // When retrieving only the aggregated data (such as covered line ratio), the file reports are not required - @OneToMany(mappedBy = "fullReport", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) - @JsonIgnoreProperties(value = { "fullReport" }, allowSetters = true) - private Set fileReports; - - // The ratio between the number of lines that are covered by all tests and the number of lines for all files in - // the corresponding (solution) submission. The attribute can take values within the range of [0, 1]. - @Column(name = "covered_line_ratio") - private Double coveredLineRatio; - - public ProgrammingSubmission getSubmission() { - return submission; - } - - public void setSubmission(ProgrammingSubmission submission) { - this.submission = submission; - } - - public Set getFileReports() { - return fileReports; - } - - public void setFileReports(Set fileReports) { - this.fileReports = fileReports; - } - - public Double getCoveredLineRatio() { - return coveredLineRatio; - } - - public void setCoveredLineRatio(Double coveredLineRatio) { - this.coveredLineRatio = coveredLineRatio; - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/domain/hestia/ExerciseHint.java b/src/main/java/de/tum/cit/aet/artemis/programming/domain/hestia/ExerciseHint.java deleted file mode 100644 index 119abdf09c53..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/domain/hestia/ExerciseHint.java +++ /dev/null @@ -1,192 +0,0 @@ -package de.tum.cit.aet.artemis.programming.domain.hestia; - -import java.util.HashSet; -import java.util.Set; - -import jakarta.persistence.CascadeType; -import jakarta.persistence.Column; -import jakarta.persistence.DiscriminatorColumn; -import jakarta.persistence.DiscriminatorType; -import jakarta.persistence.DiscriminatorValue; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.Inheritance; -import jakarta.persistence.InheritanceType; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.OneToMany; -import jakarta.persistence.Table; -import jakarta.persistence.Transient; -import jakarta.validation.constraints.Max; -import jakarta.validation.constraints.Min; - -import org.hibernate.annotations.Cache; -import org.hibernate.annotations.CacheConcurrencyStrategy; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; - -import de.tum.cit.aet.artemis.core.domain.DomainObject; -import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise; -import de.tum.cit.aet.artemis.programming.service.hestia.ExerciseHintService; - -/** - * An ExerciseHint. - */ -@Entity -@Table(name = "exercise_hint") -@Inheritance(strategy = InheritanceType.SINGLE_TABLE) -@DiscriminatorColumn(name = "discriminator", discriminatorType = DiscriminatorType.STRING) -@DiscriminatorValue("T") -@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) -@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") -// @formatter:off -@JsonSubTypes({ - @JsonSubTypes.Type(value = ExerciseHint.class, name = "text"), - @JsonSubTypes.Type(value = CodeHint.class, name = "code") -}) -// @formatter:on -@JsonInclude(JsonInclude.Include.NON_EMPTY) -public class ExerciseHint extends DomainObject { - - @Column(name = "title") - private String title; - - // A short description of this hint, so the student knows what this hint is about before activating it - @Column(name = "description", length = 1000) - private String description; - - @Column(name = "content") - private String content; - - @ManyToOne - @JsonIgnoreProperties("exerciseHints") - private ProgrammingExercise exercise; - - @ManyToOne - @JsonIgnoreProperties("exerciseHints") - private ProgrammingExerciseTask task; - - @OneToMany(mappedBy = "exerciseHint", cascade = CascadeType.REMOVE, orphanRemoval = true, fetch = FetchType.LAZY) - @JsonIgnore - private Set exerciseHintActivations = new HashSet<>(); - - @Column(name = "display_threshold", columnDefinition = "TINYINT") - @Min(0) - @Max(100) - private short displayThreshold = 3; - - @Transient - private Integer currentUserRatingTransient; - - public String getTitle() { - return title; - } - - public ExerciseHint title(String title) { - this.title = title; - return this; - } - - public void setTitle(String title) { - this.title = title; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public String getContent() { - return content; - } - - public ExerciseHint content(String content) { - this.content = content; - return this; - } - - public void setContent(String content) { - this.content = content; - } - - public ProgrammingExercise getExercise() { - return exercise; - } - - public ExerciseHint exercise(ProgrammingExercise exercise) { - this.exercise = exercise; - return this; - } - - public ProgrammingExerciseTask getProgrammingExerciseTask() { - return task; - } - - public void setProgrammingExerciseTask(ProgrammingExerciseTask programmingExerciseTask) { - this.task = programmingExerciseTask; - } - - public void setExercise(ProgrammingExercise exercise) { - this.exercise = exercise; - } - - public void removeContent() { - this.content = null; - this.title = null; - } - - @Override - public String toString() { - return "ExerciseHint{" + "id=" + getId() + ", title='" + getTitle() + "'" + ", content='" + getContent() + "'" + "}"; - } - - public Integer getCurrentUserRating() { - return currentUserRatingTransient; - } - - public void setCurrentUserRating(Integer currentUserRating) { - this.currentUserRatingTransient = currentUserRating; - } - - public Set getExerciseHintActivations() { - return exerciseHintActivations; - } - - public void setExerciseHintActivations(Set exerciseHintActivations) { - this.exerciseHintActivations = exerciseHintActivations; - } - - /** - * Returns a threshold value that defines when this exercise hint is displayed to student participating in a programming exercise. - * The algorithm defining if the hint is display is described in {@link ExerciseHintService#getAvailableExerciseHints} - * - * @return the display threshold value - */ - public short getDisplayThreshold() { - return displayThreshold; - } - - public void setDisplayThreshold(short displayThreshold) { - this.displayThreshold = displayThreshold; - } - - /** - * Creates a copy of this hint including basic attributes, but excluding attributes referencing other models - * - * @return The copied hint - */ - public ExerciseHint createCopy() { - ExerciseHint copiedHint = new ExerciseHint(); - - copiedHint.setDescription(this.getDescription()); - copiedHint.setContent(this.getContent()); - copiedHint.setTitle(this.getTitle()); - return copiedHint; - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/domain/hestia/ExerciseHintActivation.java b/src/main/java/de/tum/cit/aet/artemis/programming/domain/hestia/ExerciseHintActivation.java deleted file mode 100644 index 595c90d61abb..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/domain/hestia/ExerciseHintActivation.java +++ /dev/null @@ -1,67 +0,0 @@ -package de.tum.cit.aet.artemis.programming.domain.hestia; - -import java.time.ZonedDateTime; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.Table; - -import org.hibernate.annotations.Cache; -import org.hibernate.annotations.CacheConcurrencyStrategy; - -import com.fasterxml.jackson.annotation.JsonInclude; - -import de.tum.cit.aet.artemis.core.domain.DomainObject; -import de.tum.cit.aet.artemis.core.domain.User; - -@Entity -@Table(name = "exercise_hint_activation") -@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) -@JsonInclude(JsonInclude.Include.NON_EMPTY) -public class ExerciseHintActivation extends DomainObject { - - @ManyToOne(optional = false) - private User user; - - @ManyToOne(optional = false) - private ExerciseHint exerciseHint; - - @Column(name = "activation_date", nullable = false) - private ZonedDateTime activationDate; - - @Column(name = "rating") - private Integer rating; - - public User getUser() { - return user; - } - - public void setUser(User user) { - this.user = user; - } - - public ExerciseHint getExerciseHint() { - return exerciseHint; - } - - public void setExerciseHint(ExerciseHint exerciseHint) { - this.exerciseHint = exerciseHint; - } - - public ZonedDateTime getActivationDate() { - return activationDate; - } - - public void setActivationDate(ZonedDateTime activationDate) { - this.activationDate = activationDate; - } - - public Integer getRating() { - return rating; - } - - public void setRating(Integer rating) { - this.rating = rating; - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/domain/hestia/ProgrammingExerciseSolutionEntry.java b/src/main/java/de/tum/cit/aet/artemis/programming/domain/hestia/ProgrammingExerciseSolutionEntry.java deleted file mode 100644 index 01cf4ce871b4..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/domain/hestia/ProgrammingExerciseSolutionEntry.java +++ /dev/null @@ -1,135 +0,0 @@ -package de.tum.cit.aet.artemis.programming.domain.hestia; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.Table; - -import org.hibernate.annotations.Cache; -import org.hibernate.annotations.CacheConcurrencyStrategy; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; - -import de.tum.cit.aet.artemis.core.domain.DomainObject; -import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseTestCase; - -/** - * A ProgrammingExerciseSolutionEntry represents a single change in a file that a students has to make in order to pass the related test. - * It is structured similarly to a git diff entry. - * If it replaces existing code it will contain the previous code that it replaces otherwise previousCode will be null. - * If it is only removing existing code the code attribute will be null. - * If it encompasses the addition of an entire file, previousLine will be null. - * If it deletes an entire file, line will be null. - * previousLine and line will be different when there are other changes higher up in the file. - *

- * Example: - * A print statement gets changed: - * - * SolutionEntry { - * filePath = "<...>" - * previousLine = 12 - * previousCode = System.out.println("Tset"); - * line = 12 - * code = System.out.println("Test"); - * } - * - */ -@Entity -@Table(name = "programming_exercise_solution_entry") -@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) -@JsonInclude(JsonInclude.Include.NON_EMPTY) -public class ProgrammingExerciseSolutionEntry extends DomainObject { - - @Column(name = "file_path") - private String filePath; - - // The line at which the previous code segment is in the template - @Column(name = "previous_line") - private Integer previousLine; - - // The line at which the new code segment is in the solution - @Column(name = "line") - private Integer line; - - // The previous code segment to be replaced by the new code segment - @Column(name = "previous_code") - private String previousCode; - - // The new code segment that replaces the old code segment - @Column(name = "code") - private String code; - - // Fetched lazily, as we never need the code hint when fetching solution entries - @ManyToOne(fetch = FetchType.LAZY) - @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) - private CodeHint codeHint; - - @ManyToOne - @JsonIgnoreProperties(value = "solutionEntries", allowSetters = true) - private ProgrammingExerciseTestCase testCase; - - public String getFilePath() { - return filePath; - } - - public void setFilePath(String file) { - this.filePath = file; - } - - public Integer getPreviousLine() { - return previousLine; - } - - public void setPreviousLine(Integer previousLine) { - this.previousLine = previousLine; - } - - public Integer getLine() { - return line; - } - - public void setLine(Integer line) { - this.line = line; - } - - public String getPreviousCode() { - return previousCode; - } - - public void setPreviousCode(String previousCode) { - this.previousCode = previousCode; - } - - public String getCode() { - return code; - } - - public void setCode(String code) { - this.code = code; - } - - public CodeHint getCodeHint() { - return codeHint; - } - - public void setCodeHint(CodeHint codeHint) { - this.codeHint = codeHint; - } - - public ProgrammingExerciseTestCase getTestCase() { - return this.testCase; - } - - public void setTestCase(ProgrammingExerciseTestCase testCase) { - this.testCase = testCase; - } - - @Override - public String toString() { - return "ProgrammingExerciseSolutionEntry{" + "id=" + getId() + '\'' + ", filePath='" + filePath + '\'' + ", previousLine=" + previousLine + ", line=" + line - + ", previousCode='" + previousCode + '\'' + ", code='" + code + '\'' + '}'; - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/domain/hestia/TestwiseCoverageReportEntry.java b/src/main/java/de/tum/cit/aet/artemis/programming/domain/hestia/TestwiseCoverageReportEntry.java deleted file mode 100644 index 60383357c9dc..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/domain/hestia/TestwiseCoverageReportEntry.java +++ /dev/null @@ -1,72 +0,0 @@ -package de.tum.cit.aet.artemis.programming.domain.hestia; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.Table; - -import org.hibernate.annotations.Cache; -import org.hibernate.annotations.CacheConcurrencyStrategy; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; - -import de.tum.cit.aet.artemis.core.domain.DomainObject; -import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseTestCase; - -/** - * A single entry from testwise coverage report by file path and consecutive executed code block. - * A block is represented by the start line and the length (i.e. number of lines) of the block. - */ -@Entity -@Table(name = "testwise_coverage_report_entry") -@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) -@JsonInclude(JsonInclude.Include.NON_EMPTY) -public class TestwiseCoverageReportEntry extends DomainObject { - - @ManyToOne - @JsonIgnoreProperties("testwiseCoverageEntries") - private CoverageFileReport fileReport; - - @ManyToOne - @JsonIgnoreProperties("coverageEntries") - private ProgrammingExerciseTestCase testCase; - - @Column(name = "start_line") - private Integer startLine; - - @Column(name = "line_count") - private Integer lineCount; - - public CoverageFileReport getFileReport() { - return fileReport; - } - - public void setFileReport(CoverageFileReport fileReport) { - this.fileReport = fileReport; - } - - public ProgrammingExerciseTestCase getTestCase() { - return testCase; - } - - public void setTestCase(ProgrammingExerciseTestCase testCase) { - this.testCase = testCase; - } - - public Integer getStartLine() { - return startLine; - } - - public void setStartLine(Integer startLine) { - this.startLine = startLine; - } - - public Integer getLineCount() { - return lineCount; - } - - public void setLineCount(Integer lineCount) { - this.lineCount = lineCount; - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/dto/BuildJobDTOInterface.java b/src/main/java/de/tum/cit/aet/artemis/programming/dto/BuildJobInterface.java similarity index 74% rename from src/main/java/de/tum/cit/aet/artemis/programming/dto/BuildJobDTOInterface.java rename to src/main/java/de/tum/cit/aet/artemis/programming/dto/BuildJobInterface.java index 3d838ca6233b..0a8a7362deef 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/dto/BuildJobDTOInterface.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/dto/BuildJobInterface.java @@ -8,19 +8,19 @@ * Interface for DTOs that represent a build job. */ @JsonInclude(JsonInclude.Include.NON_EMPTY) -public interface BuildJobDTOInterface { +public interface BuildJobInterface { /** * Gets the failed tests of the build job. * * @return list of failed tests. */ - List getFailedTests(); + List failedTests(); /** * Gets the successful tests of the build job. * * @return list of successful tests. */ - List getSuccessfulTests(); + List successfulTests(); } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/dto/AbstractBuildResultNotificationDTO.java b/src/main/java/de/tum/cit/aet/artemis/programming/dto/BuildResultNotification.java similarity index 54% rename from src/main/java/de/tum/cit/aet/artemis/programming/dto/AbstractBuildResultNotificationDTO.java rename to src/main/java/de/tum/cit/aet/artemis/programming/dto/BuildResultNotification.java index 9435fdf8e463..bfad4eba2de8 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/dto/AbstractBuildResultNotificationDTO.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/dto/BuildResultNotification.java @@ -11,27 +11,25 @@ import de.tum.cit.aet.artemis.exercise.domain.SubmissionType; import de.tum.cit.aet.artemis.programming.domain.build.BuildLogEntry; -import de.tum.cit.aet.artemis.programming.service.ci.notification.dto.TestwiseCoverageReportDTO; @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_EMPTY) -// TODO: convert subclasses to records -public abstract class AbstractBuildResultNotificationDTO { +public interface BuildResultNotification { - public abstract ZonedDateTime getBuildRunDate(); + ZonedDateTime buildRunDate(); @Nullable - protected abstract String getCommitHashFromAssignmentRepo(); + String assignmentRepoCommitHash(); @Nullable - protected abstract String getCommitHashFromTestsRepo(); + String testsRepoCommitHash(); @Nullable - public abstract String getBranchNameFromAssignmentRepo(); + String assignmentRepoBranchName(); - public abstract boolean isBuildSuccessful(); + boolean isBuildSuccessful(); - public abstract Double getBuildScore(); + Double buildScore(); /** * Get the commit hash from the build result, the commit hash will be different for submission types or null. @@ -40,22 +38,22 @@ public abstract class AbstractBuildResultNotificationDTO { * @return if the commit hash for the given submission type was found, otherwise empty. */ @Nullable - public String getCommitHash(SubmissionType submissionType) { + default String commitHash(SubmissionType submissionType) { final var isAssignmentSubmission = List.of(SubmissionType.MANUAL, SubmissionType.INSTRUCTOR, SubmissionType.ILLEGAL).contains(submissionType); if (isAssignmentSubmission) { - return getCommitHashFromAssignmentRepo(); + return assignmentRepoCommitHash(); } else if (submissionType.equals(SubmissionType.TEST)) { - return getCommitHashFromTestsRepo(); + return testsRepoCommitHash(); } return null; } - public abstract boolean hasArtifact(); + boolean hasArtifact(); - public abstract boolean hasLogs(); + boolean hasLogs(); - public abstract List extractBuildLogs(); + List extractBuildLogs(); /** * Gets the build jobs that are part of the build result. @@ -63,19 +61,12 @@ else if (submissionType.equals(SubmissionType.TEST)) { * @return list of build jobs. */ @JsonIgnore - public abstract List getBuildJobs(); + List jobs(); /** * Gets the static code analysis reports that are part of the build result. * * @return list of static code analysis reports. */ - public abstract List getStaticCodeAnalysisReports(); - - /** - * Gets the test-wise coverage reports that are part of the build result. - * - * @return list of test-wise coverage reports. - */ - public abstract List getTestwiseCoverageReports(); + List staticCodeAnalysisReports(); } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/dto/ConsistencyErrorDTO.java b/src/main/java/de/tum/cit/aet/artemis/programming/dto/ConsistencyErrorDTO.java index d200a62e3422..4e4e582cbebe 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/dto/ConsistencyErrorDTO.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/dto/ConsistencyErrorDTO.java @@ -1,7 +1,5 @@ package de.tum.cit.aet.artemis.programming.dto; -import java.util.Objects; - import com.fasterxml.jackson.annotation.JsonInclude; import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise; @@ -9,63 +7,11 @@ /** * A DTO representing a consistency error */ +// TODO: use a ProgrammingExerciseDTO instead of the whole ProgrammingExercise entity @JsonInclude(JsonInclude.Include.NON_EMPTY) -public class ConsistencyErrorDTO { - - private ProgrammingExercise programmingExercise; - - private ErrorType type; - - public ConsistencyErrorDTO(ProgrammingExercise programmingExercise, ErrorType type) { - this.programmingExercise = programmingExercise; - this.type = type; - } - - public ProgrammingExercise getProgrammingExercise() { - return programmingExercise; - } - - public void setProgrammingExercise(ProgrammingExercise programmingExercise) { - this.programmingExercise = programmingExercise; - } - - public ErrorType getType() { - return type; - } - - public void setType(ErrorType type) { - this.type = type; - } +public record ConsistencyErrorDTO(ProgrammingExercise programmingExercise, ErrorType type) { public enum ErrorType { - VCS_PROJECT_MISSING, TEMPLATE_REPO_MISSING, SOLUTION_REPO_MISSING, AUXILIARY_REPO_MISSING, TEST_REPO_MISSING, TEMPLATE_BUILD_PLAN_MISSING, SOLUTION_BUILD_PLAN_MISSING; - - @Override - public String toString() { - return name(); - } - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - ConsistencyErrorDTO that = (ConsistencyErrorDTO) obj; - return Objects.equals(getProgrammingExercise(), that.getProgrammingExercise()) && getType() == that.getType(); - } - - @Override - public int hashCode() { - return Objects.hash(getProgrammingExercise(), getType()); - } - - @Override - public String toString() { - return "ConsistencyErrorDTO{" + "programmingExercise='" + programmingExercise.getTitle() + "', type='" + type.name() + "'}"; } } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/dto/CoverageReportAndSubmissionDateDTO.java b/src/main/java/de/tum/cit/aet/artemis/programming/dto/CoverageReportAndSubmissionDateDTO.java deleted file mode 100644 index a5ddfd8550d6..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/dto/CoverageReportAndSubmissionDateDTO.java +++ /dev/null @@ -1,11 +0,0 @@ -package de.tum.cit.aet.artemis.programming.dto; - -import java.time.ZonedDateTime; - -import com.fasterxml.jackson.annotation.JsonInclude; - -import de.tum.cit.aet.artemis.programming.domain.hestia.CoverageReport; - -@JsonInclude(JsonInclude.Include.NON_EMPTY) -public record CoverageReportAndSubmissionDateDTO(CoverageReport coverageReport, ZonedDateTime submissionDate) { -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/dto/ProgrammingExerciseGitDiffEntryDTO.java b/src/main/java/de/tum/cit/aet/artemis/programming/dto/ProgrammingExerciseGitDiffEntryDTO.java index 6b5f37149cd6..67707c175ab0 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/dto/ProgrammingExerciseGitDiffEntryDTO.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/dto/ProgrammingExerciseGitDiffEntryDTO.java @@ -2,7 +2,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; -import de.tum.cit.aet.artemis.programming.domain.hestia.ProgrammingExerciseGitDiffEntry; +import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseGitDiffEntry; /** * DTO for a git diff report entry. diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/dto/ProgrammingExerciseGitDiffReportDTO.java b/src/main/java/de/tum/cit/aet/artemis/programming/dto/ProgrammingExerciseGitDiffReportDTO.java index 6f027a90326b..04e32ff039d1 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/dto/ProgrammingExerciseGitDiffReportDTO.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/dto/ProgrammingExerciseGitDiffReportDTO.java @@ -5,7 +5,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; -import de.tum.cit.aet.artemis.programming.domain.hestia.ProgrammingExerciseGitDiffReport; +import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseGitDiffReport; /** * DTO for a git diff report. diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/dto/TestCaseBaseDTO.java b/src/main/java/de/tum/cit/aet/artemis/programming/dto/TestCaseBase.java similarity index 85% rename from src/main/java/de/tum/cit/aet/artemis/programming/dto/TestCaseBaseDTO.java rename to src/main/java/de/tum/cit/aet/artemis/programming/dto/TestCaseBase.java index 267caa9256db..fef892e8fb16 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/dto/TestCaseBaseDTO.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/dto/TestCaseBase.java @@ -9,14 +9,14 @@ * Interface for DTOs that represent a test case. */ @JsonInclude(JsonInclude.Include.NON_EMPTY) -public interface TestCaseBaseDTO { +public interface TestCaseBase { /** * Gets the name of the test case * * @return the name of the test case */ - String getName(); + String name(); /** * Gets the messages of the test case (typically error messages) @@ -24,5 +24,5 @@ public interface TestCaseBaseDTO { * @return the messages of the test case */ @JsonIgnore - List getTestMessages(); + List testMessages(); } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/dto/aeolus/Action.java b/src/main/java/de/tum/cit/aet/artemis/programming/dto/aeolus/Action.java new file mode 100644 index 000000000000..34f2cfb9537f --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/programming/dto/aeolus/Action.java @@ -0,0 +1,30 @@ +package de.tum.cit.aet.artemis.programming.dto.aeolus; + +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; + +/** + * Base for the actions that can be defined in a {@link Windfile} + * NOTE: you must create a record that implements this interface to specify actions + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public interface Action { + + Map parameters(); + + Map environment(); + + boolean runAlways(); + + String name(); + + List results(); + + String workdir(); + + String platform(); +} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/aeolus/AeolusRepository.java b/src/main/java/de/tum/cit/aet/artemis/programming/dto/aeolus/AeolusRepository.java similarity index 85% rename from src/main/java/de/tum/cit/aet/artemis/programming/service/aeolus/AeolusRepository.java rename to src/main/java/de/tum/cit/aet/artemis/programming/dto/aeolus/AeolusRepository.java index 6793fb84db1d..84dc2e893d88 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/aeolus/AeolusRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/dto/aeolus/AeolusRepository.java @@ -1,4 +1,4 @@ -package de.tum.cit.aet.artemis.programming.service.aeolus; +package de.tum.cit.aet.artemis.programming.dto.aeolus; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/aeolus/AeolusResult.java b/src/main/java/de/tum/cit/aet/artemis/programming/dto/aeolus/AeolusResult.java similarity index 83% rename from src/main/java/de/tum/cit/aet/artemis/programming/service/aeolus/AeolusResult.java rename to src/main/java/de/tum/cit/aet/artemis/programming/dto/aeolus/AeolusResult.java index 12f319121957..096e98701702 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/aeolus/AeolusResult.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/dto/aeolus/AeolusResult.java @@ -1,4 +1,4 @@ -package de.tum.cit.aet.artemis.programming.service.aeolus; +package de.tum.cit.aet.artemis.programming.dto.aeolus; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/aeolus/DockerConfig.java b/src/main/java/de/tum/cit/aet/artemis/programming/dto/aeolus/DockerConfig.java similarity index 95% rename from src/main/java/de/tum/cit/aet/artemis/programming/service/aeolus/DockerConfig.java rename to src/main/java/de/tum/cit/aet/artemis/programming/dto/aeolus/DockerConfig.java index 5f5161737a6a..ed8cc72283c1 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/aeolus/DockerConfig.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/dto/aeolus/DockerConfig.java @@ -1,4 +1,4 @@ -package de.tum.cit.aet.artemis.programming.service.aeolus; +package de.tum.cit.aet.artemis.programming.dto.aeolus; import java.util.List; diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/dto/aeolus/PlatformAction.java b/src/main/java/de/tum/cit/aet/artemis/programming/dto/aeolus/PlatformAction.java new file mode 100644 index 000000000000..d2bde6916175 --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/programming/dto/aeolus/PlatformAction.java @@ -0,0 +1,17 @@ +package de.tum.cit.aet.artemis.programming.dto.aeolus; + +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; + +/** + * Represents a CI action that is intended to run only on a specific target, can be used in a {@link Windfile}. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public record PlatformAction(String name, Map parameters, Map environment, List results, String workdir, boolean runAlways, + String platform, String kind, String type) implements Action { + +} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/aeolus/ScriptAction.java b/src/main/java/de/tum/cit/aet/artemis/programming/dto/aeolus/ScriptAction.java similarity index 60% rename from src/main/java/de/tum/cit/aet/artemis/programming/service/aeolus/ScriptAction.java rename to src/main/java/de/tum/cit/aet/artemis/programming/dto/aeolus/ScriptAction.java index 5006e8a850a6..9716b97deac0 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/aeolus/ScriptAction.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/dto/aeolus/ScriptAction.java @@ -1,4 +1,7 @@ -package de.tum.cit.aet.artemis.programming.service.aeolus; +package de.tum.cit.aet.artemis.programming.dto.aeolus; + +import java.util.List; +import java.util.Map; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; @@ -8,18 +11,9 @@ * independent actions but also to run actions on a single target. (e.q. the parsing of the test results that needs to * run on Jenkins but not in LocalCI) */ -// TODO: convert into Record @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_EMPTY) -public class ScriptAction extends Action { - - private String script; - - public String getScript() { - return script; - } +public record ScriptAction(String name, Map parameters, Map environment, List results, String workdir, boolean runAlways, + String platform, String script) implements Action { - public void setScript(String script) { - this.script = script; - } } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/dto/aeolus/Windfile.java b/src/main/java/de/tum/cit/aet/artemis/programming/dto/aeolus/Windfile.java new file mode 100644 index 000000000000..f975a45acc60 --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/programming/dto/aeolus/Windfile.java @@ -0,0 +1,86 @@ +package de.tum.cit.aet.artemis.programming.dto.aeolus; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; + +import de.tum.cit.aet.artemis.programming.service.aeolus.ActionDeserializer; + +/** + * Represents a windfile, the definition file for an aeolus build plan that can then be used to generate a Jenkinsfile. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public record Windfile(String api, WindfileMetadata metadata, List actions, Map repositories) { + + public Windfile { + if (actions == null) { + actions = new ArrayList<>(); + } + if (repositories == null) { + repositories = new HashMap<>(); + } + } + + private static final ObjectMapper mapper = new ObjectMapper(); + + /** + * Creates a new windfile based on an existing one with updated metadata. + * + * @param existingWindfile the existing windfile to base the new one on. + * @param metadata the metadata of the newly created windfile. + */ + public Windfile(Windfile existingWindfile, WindfileMetadata metadata) { + this(existingWindfile.api(), metadata, existingWindfile.actions(), existingWindfile.repositories()); + } + + /** + * Creates a new windfile based on an existing one with updated metadata. + * + * @param existingWindfile the existing windfile to base the new one on. + * @param metadata the metadata of the newly created windfile. + * @param repositories the repositories of the newly created windfile. + */ + public Windfile(Windfile existingWindfile, WindfileMetadata metadata, Map repositories) { + this(existingWindfile.api(), metadata, existingWindfile.actions(), repositories); + } + + /** + * Deserializes a windfile from a json string. + * + * @param json the json string to deserialize. + * @return the deserialized windfile. + * @throws JsonProcessingException if the json string is not valid. + */ + public static Windfile deserialize(String json) throws JsonProcessingException { + SimpleModule module = new SimpleModule(); + module.addDeserializer(Action.class, new ActionDeserializer()); + mapper.registerModule(module); + return mapper.readValue(json, Windfile.class); + } + + /** + * Gets the script actions of a windfile. + * + * @return the script actions of a windfile. + */ + public List scriptActions() { + return actions.stream().filter(ScriptAction.class::isInstance).map(ScriptAction.class::cast).toList(); + } + + /** + * Collects the results of all actions of a windfile. + * + * @return the results of all actions of this windfile + */ + public List results() { + return actions.stream().filter(action -> action.results() != null && !action.results().isEmpty()).flatMap(action -> action.results().stream()).toList(); + } +} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/aeolus/WindfileMetadata.java b/src/main/java/de/tum/cit/aet/artemis/programming/dto/aeolus/WindfileMetadata.java similarity index 90% rename from src/main/java/de/tum/cit/aet/artemis/programming/service/aeolus/WindfileMetadata.java rename to src/main/java/de/tum/cit/aet/artemis/programming/dto/aeolus/WindfileMetadata.java index 5e9d1547ddfa..0c5def63abd7 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/aeolus/WindfileMetadata.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/dto/aeolus/WindfileMetadata.java @@ -1,4 +1,4 @@ -package de.tum.cit.aet.artemis.programming.service.aeolus; +package de.tum.cit.aet.artemis.programming.dto.aeolus; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/repository/hestia/ProgrammingExerciseGitDiffReportRepository.java b/src/main/java/de/tum/cit/aet/artemis/programming/repository/ProgrammingExerciseGitDiffReportRepository.java similarity index 88% rename from src/main/java/de/tum/cit/aet/artemis/programming/repository/hestia/ProgrammingExerciseGitDiffReportRepository.java rename to src/main/java/de/tum/cit/aet/artemis/programming/repository/ProgrammingExerciseGitDiffReportRepository.java index d1c5b9acf8d0..37b6b9fb3522 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/repository/hestia/ProgrammingExerciseGitDiffReportRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/repository/ProgrammingExerciseGitDiffReportRepository.java @@ -1,4 +1,4 @@ -package de.tum.cit.aet.artemis.programming.repository.hestia; +package de.tum.cit.aet.artemis.programming.repository; import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; @@ -10,7 +10,7 @@ import org.springframework.transaction.annotation.Transactional; import de.tum.cit.aet.artemis.core.repository.base.ArtemisJpaRepository; -import de.tum.cit.aet.artemis.programming.domain.hestia.ProgrammingExerciseGitDiffReport; +import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseGitDiffReport; /** * Spring Data JPA repository for the ProgrammingExerciseGitDiffReport entity. diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/repository/ProgrammingExerciseRepository.java b/src/main/java/de/tum/cit/aet/artemis/programming/repository/ProgrammingExerciseRepository.java index 73ea3f0cddc3..8641ccd9e48c 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/repository/ProgrammingExerciseRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/repository/ProgrammingExerciseRepository.java @@ -314,11 +314,9 @@ default ProgrammingExercise findOneByProjectKeyOrThrow(String projectKey, boolea FROM ProgrammingExercise p LEFT JOIN FETCH p.testCases tc LEFT JOIN FETCH p.staticCodeAnalysisCategories - LEFT JOIN FETCH p.exerciseHints LEFT JOIN FETCH p.templateParticipation LEFT JOIN FETCH p.solutionParticipation LEFT JOIN FETCH p.auxiliaryRepositories - LEFT JOIN FETCH tc.solutionEntries LEFT JOIN FETCH p.buildConfig WHERE p.id = :exerciseId """) @@ -330,15 +328,13 @@ Optional findByIdWithEagerTestCasesStaticCodeAnalysisCatego FROM ProgrammingExercise p LEFT JOIN FETCH p.testCases tc LEFT JOIN FETCH p.staticCodeAnalysisCategories - LEFT JOIN FETCH p.exerciseHints LEFT JOIN FETCH p.templateParticipation LEFT JOIN FETCH p.solutionParticipation LEFT JOIN FETCH p.auxiliaryRepositories - LEFT JOIN FETCH tc.solutionEntries LEFT JOIN FETCH p.buildConfig WHERE p.id = :exerciseId """) - Optional findByIdWithEagerBuildConfigTestCasesStaticCodeAnalysisCategoriesHintsAndTemplateAndSolutionParticipationsAndAuxReposAndSolutionEntriesAndBuildConfig( + Optional findByIdWithEagerBuildConfigTestCasesStaticCodeAnalysisCategoriesAndTemplateAndSolutionParticipationsAndAuxReposAndAndBuildConfig( @Param("exerciseId") long exerciseId); @Query(""" @@ -346,11 +342,9 @@ Optional findByIdWithEagerBuildConfigTestCasesStaticCodeAna FROM ProgrammingExercise p LEFT JOIN FETCH p.testCases tc LEFT JOIN FETCH p.staticCodeAnalysisCategories - LEFT JOIN FETCH p.exerciseHints LEFT JOIN FETCH p.templateParticipation LEFT JOIN FETCH p.solutionParticipation LEFT JOIN FETCH p.auxiliaryRepositories - LEFT JOIN FETCH tc.solutionEntries LEFT JOIN FETCH p.buildConfig LEFT JOIN FETCH p.plagiarismDetectionConfig WHERE p.id = :exerciseId @@ -976,7 +970,6 @@ enum ProgrammingExerciseFetchOptions implements FetchOptions { Tasks(ProgrammingExercise_.TASKS), StaticCodeAnalysisCategories(ProgrammingExercise_.STATIC_CODE_ANALYSIS_CATEGORIES), SubmissionPolicy(ProgrammingExercise_.SUBMISSION_POLICY), - ExerciseHints(ProgrammingExercise_.EXERCISE_HINTS), CompetencyLinks(ProgrammingExercise_.COMPETENCY_LINKS), Teams(ProgrammingExercise_.TEAMS), TutorParticipations(ProgrammingExercise_.TUTOR_PARTICIPATIONS), diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/repository/hestia/ProgrammingExerciseTaskRepository.java b/src/main/java/de/tum/cit/aet/artemis/programming/repository/ProgrammingExerciseTaskRepository.java similarity index 74% rename from src/main/java/de/tum/cit/aet/artemis/programming/repository/hestia/ProgrammingExerciseTaskRepository.java rename to src/main/java/de/tum/cit/aet/artemis/programming/repository/ProgrammingExerciseTaskRepository.java index 778c0c811374..fb2f47556daf 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/repository/hestia/ProgrammingExerciseTaskRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/repository/ProgrammingExerciseTaskRepository.java @@ -1,4 +1,4 @@ -package de.tum.cit.aet.artemis.programming.repository.hestia; +package de.tum.cit.aet.artemis.programming.repository; import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; @@ -15,7 +15,7 @@ import de.tum.cit.aet.artemis.core.exception.EntityNotFoundException; import de.tum.cit.aet.artemis.core.repository.base.ArtemisJpaRepository; -import de.tum.cit.aet.artemis.programming.domain.hestia.ProgrammingExerciseTask; +import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseTask; /** * Spring Data repository for the ProgrammingExerciseTask entity. @@ -32,8 +32,8 @@ public interface ProgrammingExerciseTaskRepository extends ArtemisJpaRepository< * @throws EntityNotFoundException If the exercise with exerciseId does not exist */ @NotNull - default List findByExerciseIdWithTestCaseAndSolutionEntriesElseThrow(long exerciseId) throws EntityNotFoundException { - return getArbitraryValueElseThrow(findByExerciseIdWithTestCaseAndSolutionEntries(exerciseId), Long.toString(exerciseId)); + default List findByExerciseIdWithTestCaseElseThrow(long exerciseId) throws EntityNotFoundException { + return getArbitraryValueElseThrow(findByExerciseIdWithTestCase(exerciseId), Long.toString(exerciseId)); } /** @@ -46,11 +46,10 @@ default List findByExerciseIdWithTestCaseAndSolutionEnt SELECT t FROM ProgrammingExerciseTask t LEFT JOIN FETCH t.testCases tc - LEFT JOIN FETCH tc.solutionEntries WHERE t.exercise.id = :exerciseId AND tc.exercise.id = :exerciseId """) - Optional> findByExerciseIdWithTestCaseAndSolutionEntries(@Param("exerciseId") long exerciseId); + Optional> findByExerciseIdWithTestCase(@Param("exerciseId") long exerciseId); /** * Gets all tasks with its test cases for a programming exercise @@ -62,17 +61,7 @@ default List findByExerciseIdWithTestCaseAndSolutionEnt SELECT t FROM ProgrammingExerciseTask t LEFT JOIN FETCH t.testCases tc - LEFT JOIN FETCH tc.solutionEntries WHERE t.exercise.id = :exerciseId """) Set findByExerciseIdWithTestCases(@Param("exerciseId") Long exerciseId); - - @Query(""" - SELECT pt - FROM ProgrammingExerciseTask pt - LEFT JOIN FETCH pt.exerciseHints h - LEFT JOIN FETCH pt.testCases tc - WHERE h.id = :codeHintId - """) - Optional findByCodeHintIdWithTestCases(@Param("codeHintId") Long codeHintId); } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/repository/ProgrammingExerciseTestCaseRepository.java b/src/main/java/de/tum/cit/aet/artemis/programming/repository/ProgrammingExerciseTestCaseRepository.java index 0ec309e45e8b..3924a5de856a 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/repository/ProgrammingExerciseTestCaseRepository.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/repository/ProgrammingExerciseTestCaseRepository.java @@ -2,7 +2,6 @@ import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; -import java.util.Optional; import java.util.Set; import org.springframework.context.annotation.Profile; @@ -23,40 +22,6 @@ public interface ProgrammingExerciseTestCaseRepository extends ArtemisJpaReposit Set findByExerciseId(long exerciseId); - default ProgrammingExerciseTestCase findByIdWithExerciseElseThrow(long testCaseId) { - return getValueElseThrow(findByIdWithExercise(testCaseId), testCaseId); - } - - /** - * Returns the test case with the programming exercise - * - * @param testCaseId of the test case - * @return the test case with the programming exercise - */ - @Query(""" - SELECT tc - FROM ProgrammingExerciseTestCase tc - LEFT JOIN FETCH tc.exercise ex - WHERE tc.id = :testCaseId - """) - Optional findByIdWithExercise(@Param("testCaseId") long testCaseId); - - /** - * Returns all test cases with the associated solution entries for a programming exercise - * - * @param exerciseId of the exercise - * @param active status of the test case - * @return all test cases with the associated solution entries - */ - @Query(""" - SELECT DISTINCT tc - FROM ProgrammingExerciseTestCase tc - LEFT JOIN FETCH tc.solutionEntries se - WHERE tc.exercise.id = :exerciseId - AND tc.active = :active - """) - Set findByExerciseIdWithSolutionEntriesAndActive(@Param("exerciseId") long exerciseId, @Param("active") Boolean active); - Set findByExerciseIdAndActive(long exerciseId, Boolean active); /** diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/repository/hestia/CodeHintRepository.java b/src/main/java/de/tum/cit/aet/artemis/programming/repository/hestia/CodeHintRepository.java deleted file mode 100644 index 9c4a03766832..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/repository/hestia/CodeHintRepository.java +++ /dev/null @@ -1,50 +0,0 @@ -package de.tum.cit.aet.artemis.programming.repository.hestia; - -import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; - -import java.util.Optional; -import java.util.Set; - -import jakarta.validation.constraints.NotNull; - -import org.springframework.context.annotation.Profile; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; -import org.springframework.stereotype.Repository; - -import de.tum.cit.aet.artemis.core.exception.EntityNotFoundException; -import de.tum.cit.aet.artemis.core.repository.base.ArtemisJpaRepository; -import de.tum.cit.aet.artemis.programming.domain.hestia.CodeHint; - -/** - * Spring Data repository for the CodeHint entity. - */ -@Profile(PROFILE_CORE) -@Repository -public interface CodeHintRepository extends ArtemisJpaRepository { - - Set findByExerciseId(Long exerciseId); - - @NotNull - default CodeHint findByIdWithSolutionEntriesElseThrow(long exerciseHintId) throws EntityNotFoundException { - return getValueElseThrow(findByIdWithSolutionEntries(exerciseHintId), exerciseHintId); - } - - @Query(""" - SELECT h - FROM CodeHint h - LEFT JOIN FETCH h.task t - LEFT JOIN FETCH h.solutionEntries tc - WHERE h.id = :codeHintId - """) - Optional findByIdWithSolutionEntries(@Param("codeHintId") Long codeHintId); - - @Query(""" - SELECT h - FROM CodeHint h - LEFT JOIN FETCH h.task t - LEFT JOIN FETCH h.solutionEntries tc - WHERE t.id = :taskId - """) - Set findByTaskIdWithSolutionEntries(@Param("taskId") Long taskId); -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/repository/hestia/CoverageFileReportRepository.java b/src/main/java/de/tum/cit/aet/artemis/programming/repository/hestia/CoverageFileReportRepository.java deleted file mode 100644 index 69bcac01e5ac..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/repository/hestia/CoverageFileReportRepository.java +++ /dev/null @@ -1,15 +0,0 @@ -package de.tum.cit.aet.artemis.programming.repository.hestia; - -import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; - -import org.springframework.context.annotation.Profile; -import org.springframework.stereotype.Repository; - -import de.tum.cit.aet.artemis.core.repository.base.ArtemisJpaRepository; -import de.tum.cit.aet.artemis.programming.domain.hestia.CoverageFileReport; - -@Profile(PROFILE_CORE) -@Repository -public interface CoverageFileReportRepository extends ArtemisJpaRepository { - -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/repository/hestia/CoverageReportRepository.java b/src/main/java/de/tum/cit/aet/artemis/programming/repository/hestia/CoverageReportRepository.java deleted file mode 100644 index eefc9aeb5848..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/repository/hestia/CoverageReportRepository.java +++ /dev/null @@ -1,101 +0,0 @@ -package de.tum.cit.aet.artemis.programming.repository.hestia; - -import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; -import static org.springframework.data.jpa.repository.EntityGraph.EntityGraphType.LOAD; - -import java.util.Collections; -import java.util.List; -import java.util.Optional; - -import org.springframework.context.annotation.Profile; -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.EntityGraph; -import org.springframework.data.jpa.repository.Modifying; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; -import org.springframework.stereotype.Repository; -import org.springframework.transaction.annotation.Transactional; - -import de.tum.cit.aet.artemis.core.domain.DomainObject; -import de.tum.cit.aet.artemis.core.repository.base.ArtemisJpaRepository; -import de.tum.cit.aet.artemis.programming.domain.hestia.CoverageReport; -import de.tum.cit.aet.artemis.programming.dto.CoverageReportAndSubmissionDateDTO; - -/** - * Spring Data JPA repository for the CoverageReport entity. - */ -@Profile(PROFILE_CORE) -@Repository -public interface CoverageReportRepository extends ArtemisJpaRepository { - - Boolean existsBySubmissionId(Long submissionId); - - @Transactional // ok because of delete - @Modifying - void deleteBySubmissionId(Long submissionId); - - @Query(""" - SELECT new de.tum.cit.aet.artemis.programming.dto.CoverageReportAndSubmissionDateDTO(r, s.submissionDate) - FROM CoverageReport r - JOIN r.submission s - JOIN ProgrammingExercise pe ON s.participation = pe.solutionParticipation - WHERE pe.id = :programmingExerciseId - AND (s.type <> de.tum.cit.aet.artemis.exercise.domain.SubmissionType.ILLEGAL OR s.type IS NULL) - ORDER BY s.submissionDate DESC - """) - List findCoverageReportsByProgrammingExerciseId(@Param("programmingExerciseId") Long programmingExerciseId, Pageable pageable); - - @EntityGraph(type = LOAD, attributePaths = "submission") - List findCoverageReportsWithSubmissionByIdIn(List ids); - - /** - * Retrieves the latest coverage reports with legal submissions for a specific programming exercise, with pagination support. - * This method avoids in-memory paging by retrieving the coverage report IDs directly from the database. - * - * @param programmingExerciseId the ID of the programming exercise to retrieve the coverage reports for - * @param pageable the pagination information - * @return a list of {@code CoverageReport} with legal submissions, or an empty list if no reports are found - */ - default List getLatestCoverageReportsWithLegalSubmissionsForProgrammingExercise(Long programmingExerciseId, Pageable pageable) { - List ids = findCoverageReportsByProgrammingExerciseId(programmingExerciseId, pageable).stream().map(CoverageReportAndSubmissionDateDTO::coverageReport) - .map(DomainObject::getId).toList(); - if (ids.isEmpty()) { - return Collections.emptyList(); - } - return findCoverageReportsWithSubmissionByIdIn(ids); - } - - @EntityGraph(type = LOAD, attributePaths = { "submission", "fileReports", "fileReports.testwiseCoverageEntries" }) - List findDistinctCoverageReportsWithEagerRelationshipsByIdIn(List ids); - - /** - * Retrieves the latest coverage reports with legal submissions for a specific programming exercise, including eager loading of file reports and entries, with pagination - * support. - * This method avoids in-memory paging by retrieving the coverage report IDs directly from the database. - * - * @param programmingExerciseId the ID of the programming exercise to retrieve the coverage reports for - * @param pageable the pagination information - * @return a list of distinct {@code CoverageReport} with eager relationships, or an empty list if no reports are found - */ - default List getLatestCoverageReportsForLegalSubmissionsForProgrammingExerciseWithEagerFileReportsAndEntries(Long programmingExerciseId, Pageable pageable) { - List ids = findCoverageReportsByProgrammingExerciseId(programmingExerciseId, pageable).stream().map(CoverageReportAndSubmissionDateDTO::coverageReport) - .map(DomainObject::getId).toList(); - if (ids.isEmpty()) { - return Collections.emptyList(); - } - return findDistinctCoverageReportsWithEagerRelationshipsByIdIn(ids); - } - - @Query(""" - SELECT DISTINCT r - FROM CoverageReport r - LEFT JOIN FETCH r.fileReports f - LEFT JOIN FETCH f.testwiseCoverageEntries - WHERE r.id = :coverageReportId - """) - Optional findCoverageReportByIdWithEagerFileReportsAndEntries(@Param("coverageReportId") Long coverageReportId); - - default CoverageReport findCoverageReportByIdWithEagerFileReportsAndEntriesElseThrow(Long coverageReportId) { - return getValueElseThrow(findCoverageReportByIdWithEagerFileReportsAndEntries(coverageReportId), coverageReportId); - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/repository/hestia/ExerciseHintActivationRepository.java b/src/main/java/de/tum/cit/aet/artemis/programming/repository/hestia/ExerciseHintActivationRepository.java deleted file mode 100644 index c827a9b3052b..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/repository/hestia/ExerciseHintActivationRepository.java +++ /dev/null @@ -1,41 +0,0 @@ -package de.tum.cit.aet.artemis.programming.repository.hestia; - -import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; - -import java.util.Optional; -import java.util.Set; - -import org.springframework.context.annotation.Profile; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; -import org.springframework.stereotype.Repository; - -import de.tum.cit.aet.artemis.core.repository.base.ArtemisJpaRepository; -import de.tum.cit.aet.artemis.programming.domain.hestia.ExerciseHintActivation; - -@Profile(PROFILE_CORE) -@Repository -public interface ExerciseHintActivationRepository extends ArtemisJpaRepository { - - @Query(""" - SELECT hintActivation - FROM ExerciseHintActivation hintActivation - LEFT JOIN FETCH hintActivation.exerciseHint hint - LEFT JOIN FETCH hint.solutionEntries - WHERE hintActivation.exerciseHint.exercise.id = :exerciseId - AND hintActivation.user.id = :userId - """) - Set findByExerciseAndUserWithExerciseHintRelations(@Param("exerciseId") long exerciseId, @Param("userId") long userId); - - @Query(""" - SELECT hintActivation - FROM ExerciseHintActivation hintActivation - WHERE hintActivation.exerciseHint.id = :exerciseHintId - AND hintActivation.user.id = :userId - """) - Optional findByExerciseHintAndUser(@Param("exerciseHintId") long exerciseHintId, @Param("userId") long userId); - - default ExerciseHintActivation findByExerciseHintAndUserElseThrow(long exerciseHintId, long userId) { - return getValueElseThrow(findByExerciseHintAndUser(exerciseHintId, userId)); - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/repository/hestia/ExerciseHintRepository.java b/src/main/java/de/tum/cit/aet/artemis/programming/repository/hestia/ExerciseHintRepository.java deleted file mode 100644 index a5f0eb4942ca..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/repository/hestia/ExerciseHintRepository.java +++ /dev/null @@ -1,50 +0,0 @@ -package de.tum.cit.aet.artemis.programming.repository.hestia; - -import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; - -import java.util.Optional; -import java.util.Set; - -import jakarta.validation.constraints.NotNull; - -import org.springframework.context.annotation.Profile; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; -import org.springframework.stereotype.Repository; - -import de.tum.cit.aet.artemis.core.exception.EntityNotFoundException; -import de.tum.cit.aet.artemis.core.repository.base.ArtemisJpaRepository; -import de.tum.cit.aet.artemis.programming.domain.hestia.ExerciseHint; - -/** - * Spring Data repository for the ExerciseHint entity. - */ -@Profile(PROFILE_CORE) -@Repository -public interface ExerciseHintRepository extends ArtemisJpaRepository { - - @Query(""" - SELECT h - FROM ExerciseHint h - LEFT JOIN FETCH h.solutionEntries se - WHERE h.id = :hintId - """) - Optional findByIdWithRelations(@Param("hintId") Long hintId); - - @NotNull - default ExerciseHint findByIdWithRelationsElseThrow(long hintId) throws EntityNotFoundException { - return getValueElseThrow(findByIdWithRelations(hintId), hintId); - } - - Set findByExerciseId(Long exerciseId); - - @Query(""" - SELECT h - FROM ExerciseHint h - LEFT JOIN FETCH h.solutionEntries se - WHERE h.exercise.id = :exerciseId - """) - Set findByExerciseIdWithRelations(@Param("exerciseId") Long exerciseId); - - Set findByTaskId(Long taskId); -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/repository/hestia/ProgrammingExerciseSolutionEntryRepository.java b/src/main/java/de/tum/cit/aet/artemis/programming/repository/hestia/ProgrammingExerciseSolutionEntryRepository.java deleted file mode 100644 index 14a03ed49c0a..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/repository/hestia/ProgrammingExerciseSolutionEntryRepository.java +++ /dev/null @@ -1,83 +0,0 @@ -package de.tum.cit.aet.artemis.programming.repository.hestia; - -import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; - -import java.util.Optional; -import java.util.Set; - -import jakarta.validation.constraints.NotNull; - -import org.springframework.context.annotation.Profile; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; -import org.springframework.stereotype.Repository; - -import de.tum.cit.aet.artemis.core.exception.EntityNotFoundException; -import de.tum.cit.aet.artemis.core.repository.base.ArtemisJpaRepository; -import de.tum.cit.aet.artemis.programming.domain.hestia.ProgrammingExerciseSolutionEntry; - -/** - * Spring Data repository for the ProgrammingExerciseSolutionEntry entity. - */ -@Profile(PROFILE_CORE) -@Repository -public interface ProgrammingExerciseSolutionEntryRepository extends ArtemisJpaRepository { - - /** - * Gets a solution entry with its test cases and programming exercise - * - * @param entryId The id of the solution entry - * @return The solution entry with the given ID if found - * @throws EntityNotFoundException If no solution entry with the given ID was found - */ - @NotNull - default ProgrammingExerciseSolutionEntry findByIdWithTestCaseAndProgrammingExerciseElseThrow(long entryId) throws EntityNotFoundException { - return getValueElseThrow(findByIdWithTestCaseAndProgrammingExercise(entryId), entryId); - } - - @Query(""" - SELECT se - FROM ProgrammingExerciseSolutionEntry se - LEFT JOIN FETCH se.testCase tc - LEFT JOIN FETCH tc.exercise pe - WHERE pe.id = :exerciseId - """) - Set findByExerciseIdWithTestCases(@Param("exerciseId") long exerciseId); - - /** - * Gets a solution entry with its test cases and programming exercise - * - * @param entryId The id of the solution entry - * @return The solution entry with the given ID - */ - @Query(""" - SELECT se - FROM ProgrammingExerciseSolutionEntry se - LEFT JOIN FETCH se.testCase tc - LEFT JOIN FETCH tc.exercise pe - WHERE se.id = :entryId - """) - Optional findByIdWithTestCaseAndProgrammingExercise(@Param("entryId") long entryId); - - @Query(""" - SELECT h.solutionEntries - FROM CodeHint h - WHERE h.id = :codeHintId - """) - Set findByCodeHintId(@Param("codeHintId") Long codeHintId); - - @Query(""" - SELECT t.solutionEntries - FROM ProgrammingExerciseTestCase t - WHERE t.id = :testCaseId - """) - Set findByTestCaseId(@Param("testCaseId") Long testCaseId); - - @Query(""" - SELECT se - FROM ProgrammingExerciseSolutionEntry se - LEFT JOIN FETCH se.codeHint - WHERE se.testCase.id = :testCaseId - """) - Set findByTestCaseIdWithCodeHint(@Param("testCaseId") Long testCaseId); -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/repository/hestia/TestwiseCoverageReportEntryRepository.java b/src/main/java/de/tum/cit/aet/artemis/programming/repository/hestia/TestwiseCoverageReportEntryRepository.java deleted file mode 100644 index 253adf77f45a..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/repository/hestia/TestwiseCoverageReportEntryRepository.java +++ /dev/null @@ -1,18 +0,0 @@ -package de.tum.cit.aet.artemis.programming.repository.hestia; - -import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; - -import org.springframework.context.annotation.Profile; -import org.springframework.stereotype.Repository; - -import de.tum.cit.aet.artemis.core.repository.base.ArtemisJpaRepository; -import de.tum.cit.aet.artemis.programming.domain.hestia.TestwiseCoverageReportEntry; - -/** - * Spring Data JPA repository for the TestwiseCoverageReportEntry entity. - */ -@Profile(PROFILE_CORE) -@Repository -public interface TestwiseCoverageReportEntryRepository extends ArtemisJpaRepository { - -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/BuildLogEntryService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/BuildLogEntryService.java index 039fce2c76bc..d1b016f7ba81 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/BuildLogEntryService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/BuildLogEntryService.java @@ -2,12 +2,15 @@ import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; import java.time.ZonedDateTime; +import java.time.format.DateTimeParseException; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -399,8 +402,6 @@ private ProgrammingExercise retrieveProgrammingExerciseByBuildJobId(String build * In case of an error during file deletion, it logs the error and continues processing. *

* - * @throws IOException if an I/O error occurs while accessing the build log files directory or - * deleting files. */ @Scheduled(cron = "${artemis.continuous-integration.build-log.cleanup-schedule:0 0 3 * * ?}") public void deleteOldBuildLogsFiles() { @@ -491,4 +492,53 @@ public boolean buildJobHasLogFile(String buildJobId, ProgrammingExercise program return Files.exists(logPath); } + /** + * Parses the build log entries from a given file and returns them as a list of {@link BuildLogDTO} objects. + * + *

+ * The method reads the file line by line and splits each line into a timestamp and a log message. + * The timestamp is expected to be separated from the log message by a tab character. + * If the timestamp cannot be parsed, the log message is appended to the previous entry. + *

+ * + * @param buildLog The {@link FileSystemResource} representing the build log file. + * @return A list of {@link BuildLogDTO} objects containing the parsed build log entries. + */ + public List parseBuildLogEntries(FileSystemResource buildLog) { + try { + List buildLogEntries = new ArrayList<>(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(buildLog.getInputStream()))) { + String line; + while ((line = reader.readLine()) != null) { + // split into timestamp and log message + int logMessageParts = 2; + String[] parts = line.split("\t", logMessageParts); + if (parts.length == logMessageParts) { + try { + ZonedDateTime time = ZonedDateTime.parse(parts[0]); + buildLogEntries.add(new BuildLogDTO(time, parts[1])); + } + catch (DateTimeParseException e) { + // If the time cannot be parsed, append the line to the last entry + if (!buildLogEntries.isEmpty()) { + BuildLogDTO lastEntry = buildLogEntries.getLast(); + buildLogEntries.set(buildLogEntries.size() - 1, new BuildLogDTO(lastEntry.time(), lastEntry.log() + "\n\t" + line)); + } + } + } + else { + // If the line does not contain a tab, add it to in a new entry + BuildLogDTO lastEntry = buildLogEntries.getLast(); + buildLogEntries.add(new BuildLogDTO(lastEntry.time(), line)); + } + } + } + return buildLogEntries; + } + catch (IOException e) { + log.error("Error occurred while trying to parse build log entries", e); + return new ArrayList<>(); + } + } + } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/BuildScriptProviderService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/BuildScriptProviderService.java index 487c92b3c21d..2d6e9bff2786 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/BuildScriptProviderService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/BuildScriptProviderService.java @@ -103,13 +103,11 @@ public String getCachedScript(String key) { * @param projectType the project type for which the template file should be returned. If omitted, a default depending on the language will be used. * @param staticAnalysis whether the static analysis template should be used * @param sequentialRuns whether the sequential runs template should be used - * @param testCoverage whether the test coverage template should be used * @return the requested template as a bash script * @throws IOException if the file does not exist */ - public String getScriptFor(ProgrammingLanguage programmingLanguage, Optional projectType, Boolean staticAnalysis, Boolean sequentialRuns, Boolean testCoverage) - throws IOException { - String templateFileName = buildTemplateName(projectType, staticAnalysis, sequentialRuns, testCoverage, "sh"); + public String getScriptFor(ProgrammingLanguage programmingLanguage, Optional projectType, Boolean staticAnalysis, Boolean sequentialRuns) throws IOException { + String templateFileName = buildTemplateName(projectType, staticAnalysis, sequentialRuns, "sh"); String uniqueKey = programmingLanguage.name().toLowerCase() + "_" + templateFileName; if (scriptCache.containsKey(uniqueKey)) { log.debug("Returning cached script for {}", uniqueKey); @@ -139,7 +137,7 @@ public String getScriptFor(ProgrammingExercise exercise) { try { ProgrammingExerciseBuildConfig buildConfig = exercise.getBuildConfig(); return getScriptFor(exercise.getProgrammingLanguage(), Optional.ofNullable(exercise.getProjectType()), exercise.isStaticCodeAnalysisEnabled(), - buildConfig.hasSequentialTestRuns(), buildConfig.isTestwiseCoverageEnabled()); + buildConfig.hasSequentialTestRuns()); } catch (IOException e) { log.error("Failed to provide build script for programming exercise " + exercise.getId(), e); @@ -153,11 +151,10 @@ public String getScriptFor(ProgrammingExercise exercise) { * @param projectType The project type for which the template file should be returned. If omitted, a default depending on the language will be used. * @param staticAnalysis whether the static analysis template should be used * @param sequentialRuns whether the sequential runs template should be used - * @param testCoverage whether the test coverage template should be used * @param fileExtension the file extension of the template file * @return The filename of the requested configuration */ - public String buildTemplateName(Optional projectType, Boolean staticAnalysis, Boolean sequentialRuns, Boolean testCoverage, String fileExtension) { + public String buildTemplateName(Optional projectType, Boolean staticAnalysis, Boolean sequentialRuns, String fileExtension) { List fileNameComponents = new ArrayList<>(); if (ProjectType.MAVEN_BLACKBOX.equals(projectType.orElse(null))) { @@ -173,9 +170,6 @@ public String buildTemplateName(Optional projectType, Boolean stati if (sequentialRuns) { fileNameComponents.add("sequential"); } - if (testCoverage) { - fileNameComponents.add("coverage"); - } return String.join("_", fileNameComponents) + "." + fileExtension; } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/CommitHistoryService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/CommitHistoryService.java index 9ee663c55c96..bf4f8496cd93 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/CommitHistoryService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/CommitHistoryService.java @@ -19,11 +19,10 @@ import org.springframework.stereotype.Service; import de.tum.cit.aet.artemis.core.service.ProfileService; +import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseGitDiffEntry; +import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseGitDiffReport; import de.tum.cit.aet.artemis.programming.domain.Repository; import de.tum.cit.aet.artemis.programming.domain.VcsRepositoryUri; -import de.tum.cit.aet.artemis.programming.domain.hestia.ProgrammingExerciseGitDiffEntry; -import de.tum.cit.aet.artemis.programming.domain.hestia.ProgrammingExerciseGitDiffReport; -import de.tum.cit.aet.artemis.programming.web.GitDiffReportParserService; @Profile(PROFILE_CORE) @Service diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/GenericBuildScriptGenerationService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/GenericBuildScriptGenerationService.java index adabad775f23..5d8008d0c833 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/GenericBuildScriptGenerationService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/GenericBuildScriptGenerationService.java @@ -34,7 +34,7 @@ public String getScript(ProgrammingExercise programmingExercise) { try { ProgrammingExerciseBuildConfig buildConfig = programmingExercise.getBuildConfig(); return buildScriptProviderService.getScriptFor(programmingExercise.getProgrammingLanguage(), Optional.ofNullable(programmingExercise.getProjectType()), - programmingExercise.isStaticCodeAnalysisEnabled(), buildConfig.hasSequentialTestRuns(), buildConfig.isTestwiseCoverageEnabled()); + programmingExercise.isStaticCodeAnalysisEnabled(), buildConfig.hasSequentialTestRuns()); } catch (IOException e) { log.error("Failed to generate build script for programming exercise " + programmingExercise.getId(), e); diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/web/GitDiffReportParserService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/GitDiffReportParserService.java similarity index 98% rename from src/main/java/de/tum/cit/aet/artemis/programming/web/GitDiffReportParserService.java rename to src/main/java/de/tum/cit/aet/artemis/programming/service/GitDiffReportParserService.java index 37042b8cdb06..f66d85d65544 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/web/GitDiffReportParserService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/GitDiffReportParserService.java @@ -1,4 +1,4 @@ -package de.tum.cit.aet.artemis.programming.web; +package de.tum.cit.aet.artemis.programming.service; import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; @@ -13,7 +13,7 @@ import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; -import de.tum.cit.aet.artemis.programming.domain.hestia.ProgrammingExerciseGitDiffEntry; +import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseGitDiffEntry; @Profile(PROFILE_CORE) @Service diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingAssessmentService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingAssessmentService.java index f62bde6cc957..139cc68a4cf7 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingAssessmentService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingAssessmentService.java @@ -90,8 +90,6 @@ private Result saveManualAssessment(Result result, User assessor) { * @return the new saved result */ public Result saveAndSubmitManualAssessment(StudentParticipation participation, Result newManualResult, Result existingManualResult, User assessor, boolean submit) { - // long feedback text is deleted as it otherwise causes duplicate entries errors and will be saved again with {@link resultRepository.save} - resultService.deleteLongFeedback(newManualResult.getFeedbacks(), newManualResult); // make sure that the submission cannot be manipulated on the client side var submission = (ProgrammingSubmission) existingManualResult.getSubmission(); ProgrammingExercise exercise = (ProgrammingExercise) participation.getExercise(); diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseExportService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseExportService.java index 20d98af84bc8..76a140eb2147 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseExportService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseExportService.java @@ -74,7 +74,6 @@ import de.tum.cit.aet.artemis.programming.repository.AuxiliaryRepositoryRepository; import de.tum.cit.aet.artemis.programming.repository.BuildPlanRepository; import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseRepository; -import de.tum.cit.aet.artemis.programming.service.hestia.ProgrammingExerciseTaskService; /** * Service for exporting programming exercises. diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseFeedbackCreationService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseFeedbackCreationService.java index d22c13ebe3d8..a75efa7bf1c2 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseFeedbackCreationService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseFeedbackCreationService.java @@ -37,18 +37,17 @@ import de.tum.cit.aet.artemis.core.config.StaticCodeAnalysisConfigurer; import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise; import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseTestCase; +import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseTestCaseType; import de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage; import de.tum.cit.aet.artemis.programming.domain.StaticCodeAnalysisCategory; import de.tum.cit.aet.artemis.programming.domain.StaticCodeAnalysisDefaultCategory; import de.tum.cit.aet.artemis.programming.domain.StaticCodeAnalysisTool; -import de.tum.cit.aet.artemis.programming.domain.hestia.ProgrammingExerciseTestCaseType; -import de.tum.cit.aet.artemis.programming.dto.AbstractBuildResultNotificationDTO; +import de.tum.cit.aet.artemis.programming.dto.BuildResultNotification; import de.tum.cit.aet.artemis.programming.dto.StaticCodeAnalysisIssue; import de.tum.cit.aet.artemis.programming.dto.StaticCodeAnalysisReportDTO; import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseRepository; import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseTestCaseRepository; import de.tum.cit.aet.artemis.programming.repository.StaticCodeAnalysisCategoryRepository; -import de.tum.cit.aet.artemis.programming.service.hestia.ProgrammingExerciseTaskService; /** * Service for creating feedback for programming exercises. @@ -278,7 +277,7 @@ else if (!testMessages.isEmpty()) { * @param buildResult from which to extract the test cases. * @param exercise the programming exercise for which the test cases should be extracted from the new result */ - public void extractTestCasesFromResultAndBroadcastUpdates(AbstractBuildResultNotificationDTO buildResult, ProgrammingExercise exercise) { + public void extractTestCasesFromResultAndBroadcastUpdates(BuildResultNotification buildResult, ProgrammingExercise exercise) { boolean haveTestCasesChanged = generateTestCasesFromBuildResult(buildResult, exercise); if (haveTestCasesChanged) { // Notify the client about the updated testCases @@ -296,7 +295,7 @@ public void extractTestCasesFromResultAndBroadcastUpdates(AbstractBuildResultNot * @param exercise programming exercise. * @return Returns true if the test cases have changed, false if they haven't. */ - public boolean generateTestCasesFromBuildResult(AbstractBuildResultNotificationDTO buildResult, ProgrammingExercise exercise) { + public boolean generateTestCasesFromBuildResult(BuildResultNotification buildResult, ProgrammingExercise exercise) { Set existingTestCases = testCaseRepository.findByExerciseId(exercise.getId()); // Do not generate test cases for static code analysis feedback Set testCasesFromFeedbacks = getTestCasesFromBuildResult(buildResult, exercise); @@ -375,12 +374,12 @@ public void setTestCaseType(Set testCases, Programm }); } - private Set getTestCasesFromBuildResult(AbstractBuildResultNotificationDTO buildResult, ProgrammingExercise exercise) { + private Set getTestCasesFromBuildResult(BuildResultNotification buildResult, ProgrammingExercise exercise) { Visibility defaultVisibility = exercise.getDefaultTestCaseVisibility(); - return buildResult.getBuildJobs().stream().flatMap(job -> Stream.concat(job.getFailedTests().stream(), job.getSuccessfulTests().stream())) + return buildResult.jobs().stream().flatMap(job -> Stream.concat(job.failedTests().stream(), job.successfulTests().stream())) // we use default values for weight, bonus multiplier and bonus points - .map(testCase -> new ProgrammingExerciseTestCase().testName(testCase.getName()).weight(1.0).bonusMultiplier(1.0).bonusPoints(0.0).exercise(exercise).active(true) + .map(testCase -> new ProgrammingExerciseTestCase().testName(testCase.name()).weight(1.0).bonusMultiplier(1.0).bonusPoints(0.0).exercise(exercise).active(true) .visibility(defaultVisibility)) .collect(Collectors.toSet()); } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/ProgrammingExerciseGitDiffReportService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseGitDiffReportService.java similarity index 97% rename from src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/ProgrammingExerciseGitDiffReportService.java rename to src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseGitDiffReportService.java index 6950e1216df5..785d200ad349 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/ProgrammingExerciseGitDiffReportService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseGitDiffReportService.java @@ -1,4 +1,4 @@ -package de.tum.cit.aet.artemis.programming.service.hestia; +package de.tum.cit.aet.artemis.programming.service; import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; @@ -24,21 +24,19 @@ import de.tum.cit.aet.artemis.core.exception.InternalServerErrorException; import de.tum.cit.aet.artemis.core.service.FileService; import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise; +import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseGitDiffEntry; +import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseGitDiffReport; import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseParticipation; import de.tum.cit.aet.artemis.programming.domain.ProgrammingSubmission; import de.tum.cit.aet.artemis.programming.domain.Repository; import de.tum.cit.aet.artemis.programming.domain.SolutionProgrammingExerciseParticipation; import de.tum.cit.aet.artemis.programming.domain.TemplateProgrammingExerciseParticipation; import de.tum.cit.aet.artemis.programming.domain.VcsRepositoryUri; -import de.tum.cit.aet.artemis.programming.domain.hestia.ProgrammingExerciseGitDiffEntry; -import de.tum.cit.aet.artemis.programming.domain.hestia.ProgrammingExerciseGitDiffReport; +import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseGitDiffReportRepository; import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseRepository; import de.tum.cit.aet.artemis.programming.repository.ProgrammingSubmissionRepository; import de.tum.cit.aet.artemis.programming.repository.SolutionProgrammingExerciseParticipationRepository; import de.tum.cit.aet.artemis.programming.repository.TemplateProgrammingExerciseParticipationRepository; -import de.tum.cit.aet.artemis.programming.repository.hestia.ProgrammingExerciseGitDiffReportRepository; -import de.tum.cit.aet.artemis.programming.service.GitService; -import de.tum.cit.aet.artemis.programming.web.GitDiffReportParserService; /** * The service handling ProgrammingExerciseGitDiffReport and their ProgrammingExerciseGitDiffEntries. diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseGradingService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseGradingService.java index 7738b6615b2f..1aff8fca66f0 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseGradingService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseGradingService.java @@ -61,7 +61,7 @@ import de.tum.cit.aet.artemis.programming.domain.submissionpolicy.LockRepositoryPolicy; import de.tum.cit.aet.artemis.programming.domain.submissionpolicy.SubmissionPenaltyPolicy; import de.tum.cit.aet.artemis.programming.domain.submissionpolicy.SubmissionPolicy; -import de.tum.cit.aet.artemis.programming.dto.AbstractBuildResultNotificationDTO; +import de.tum.cit.aet.artemis.programming.dto.BuildResultNotification; import de.tum.cit.aet.artemis.programming.dto.ProgrammingExerciseGradingStatisticsDTO; import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseRepository; import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseTestCaseRepository; @@ -218,9 +218,8 @@ public Result processNewProgrammingExerciseResult(@NotNull ProgrammingExercisePa * @param buildResult The build result received from the CI system. * @throws IllegalArgumentException Thrown if the result does not belong to the default branch of the exercise. */ - private void checkCorrectBranchElseThrow(final ProgrammingExerciseParticipation participation, final AbstractBuildResultNotificationDTO buildResult) - throws IllegalArgumentException { - var branchName = buildResult.getBranchNameFromAssignmentRepo(); + private void checkCorrectBranchElseThrow(final ProgrammingExerciseParticipation participation, final BuildResultNotification buildResult) throws IllegalArgumentException { + var branchName = buildResult.assignmentRepoBranchName(); // If the branch is not present, it might be because the assignment repo did not change because only the test repo was changed if (!ObjectUtils.isEmpty(branchName)) { String participationDefaultBranch = null; @@ -243,8 +242,8 @@ private void checkCorrectBranchElseThrow(final ProgrammingExerciseParticipation * * @param buildResult The build result received from the CI system. */ - private void checkHasCommitHashElseThrow(final AbstractBuildResultNotificationDTO buildResult) { - if (StringUtils.isEmpty(buildResult.getCommitHash(SubmissionType.MANUAL))) { + private void checkHasCommitHashElseThrow(final BuildResultNotification buildResult) { + if (StringUtils.isEmpty(buildResult.commitHash(SubmissionType.MANUAL))) { throw new IllegalArgumentException("The provided result does not specify the assignment commit hash. The result will not get processed."); } } @@ -256,28 +255,28 @@ private void checkHasCommitHashElseThrow(final AbstractBuildResultNotificationDT * @param buildResult The build result * @return The submission or empty if no submissions exist */ - protected Optional getSubmissionForBuildResult(Long participationId, AbstractBuildResultNotificationDTO buildResult) { + protected Optional getSubmissionForBuildResult(Long participationId, BuildResultNotification buildResult) { var submissions = programmingSubmissionRepository.findAllByParticipationIdWithResults(participationId); if (submissions.isEmpty()) { return Optional.empty(); } return submissions.stream().filter(theSubmission -> { - var commitHash = buildResult.getCommitHash(theSubmission.getType()); + var commitHash = buildResult.commitHash(theSubmission.getType()); return !ObjectUtils.isEmpty(commitHash) && commitHash.equals(theSubmission.getCommitHash()); }).max(Comparator.naturalOrder()); } @NotNull - protected ProgrammingSubmission createAndSaveFallbackSubmission(ProgrammingExerciseParticipation participation, AbstractBuildResultNotificationDTO buildResult) { - final var commitHash = buildResult.getCommitHash(SubmissionType.MANUAL); + protected ProgrammingSubmission createAndSaveFallbackSubmission(ProgrammingExerciseParticipation participation, BuildResultNotification buildResult) { + final var commitHash = buildResult.commitHash(SubmissionType.MANUAL); if (ObjectUtils.isEmpty(commitHash)) { log.error("Could not find commit hash for participation {}, build plan {}", participation.getId(), participation.getBuildPlanId()); } log.warn("Could not find pending ProgrammingSubmission for Commit Hash {} (Participation {}, Build Plan {}). Will create a new one subsequently...", commitHash, participation.getId(), participation.getBuildPlanId()); // We always take the build run date as the fallback solution - ZonedDateTime submissionDate = buildResult.getBuildRunDate(); + ZonedDateTime submissionDate = buildResult.buildRunDate(); if (!ObjectUtils.isEmpty(commitHash)) { try { // Try to get the actual date, the push might be 10s - 3min earlier, depending on how long the build takes. diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseImportBasicService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseImportBasicService.java index 8f5c1b427390..92bddf8f75d4 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseImportBasicService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseImportBasicService.java @@ -9,6 +9,7 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; import org.springframework.context.annotation.Profile; @@ -22,34 +23,24 @@ import de.tum.cit.aet.artemis.programming.domain.AuxiliaryRepository; import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise; import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseBuildConfig; +import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseTask; import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseTestCase; import de.tum.cit.aet.artemis.programming.domain.RepositoryType; import de.tum.cit.aet.artemis.programming.domain.StaticCodeAnalysisCategory; -import de.tum.cit.aet.artemis.programming.domain.hestia.CodeHint; -import de.tum.cit.aet.artemis.programming.domain.hestia.ProgrammingExerciseSolutionEntry; -import de.tum.cit.aet.artemis.programming.domain.hestia.ProgrammingExerciseTask; import de.tum.cit.aet.artemis.programming.domain.submissionpolicy.SubmissionPolicy; import de.tum.cit.aet.artemis.programming.repository.AuxiliaryRepositoryRepository; import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseBuildConfigRepository; import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseRepository; +import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseTaskRepository; import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseTestCaseRepository; import de.tum.cit.aet.artemis.programming.repository.StaticCodeAnalysisCategoryRepository; import de.tum.cit.aet.artemis.programming.repository.SubmissionPolicyRepository; -import de.tum.cit.aet.artemis.programming.repository.hestia.ExerciseHintRepository; -import de.tum.cit.aet.artemis.programming.repository.hestia.ProgrammingExerciseSolutionEntryRepository; -import de.tum.cit.aet.artemis.programming.repository.hestia.ProgrammingExerciseTaskRepository; -import de.tum.cit.aet.artemis.programming.service.hestia.ExerciseHintService; -import de.tum.cit.aet.artemis.programming.service.hestia.ProgrammingExerciseTaskService; import de.tum.cit.aet.artemis.programming.service.vcs.VersionControlService; @Profile(PROFILE_CORE) @Service public class ProgrammingExerciseImportBasicService { - private final ExerciseHintService exerciseHintService; - - private final ExerciseHintRepository exerciseHintRepository; - private final Optional versionControlService; private final ProgrammingExerciseParticipationService programmingExerciseParticipationService; @@ -74,22 +65,17 @@ public class ProgrammingExerciseImportBasicService { private final ProgrammingExerciseTaskService programmingExerciseTaskService; - private final ProgrammingExerciseSolutionEntryRepository solutionEntryRepository; - private final ChannelService channelService; private final ExerciseService exerciseService; - public ProgrammingExerciseImportBasicService(ExerciseHintService exerciseHintService, ExerciseHintRepository exerciseHintRepository, - Optional versionControlService, ProgrammingExerciseParticipationService programmingExerciseParticipationService, - ProgrammingExerciseTestCaseRepository programmingExerciseTestCaseRepository, StaticCodeAnalysisCategoryRepository staticCodeAnalysisCategoryRepository, - ProgrammingExerciseRepository programmingExerciseRepository, ProgrammingExerciseService programmingExerciseService, StaticCodeAnalysisService staticCodeAnalysisService, - AuxiliaryRepositoryRepository auxiliaryRepositoryRepository, SubmissionPolicyRepository submissionPolicyRepository, - ProgrammingExerciseTaskRepository programmingExerciseTaskRepository, ProgrammingExerciseTaskService programmingExerciseTaskService, - ProgrammingExerciseSolutionEntryRepository solutionEntryRepository, ChannelService channelService, + public ProgrammingExerciseImportBasicService(Optional versionControlService, + ProgrammingExerciseParticipationService programmingExerciseParticipationService, ProgrammingExerciseTestCaseRepository programmingExerciseTestCaseRepository, + StaticCodeAnalysisCategoryRepository staticCodeAnalysisCategoryRepository, ProgrammingExerciseRepository programmingExerciseRepository, + ProgrammingExerciseService programmingExerciseService, StaticCodeAnalysisService staticCodeAnalysisService, AuxiliaryRepositoryRepository auxiliaryRepositoryRepository, + SubmissionPolicyRepository submissionPolicyRepository, ProgrammingExerciseTaskRepository programmingExerciseTaskRepository, + ProgrammingExerciseTaskService programmingExerciseTaskService, ChannelService channelService, ProgrammingExerciseBuildConfigRepository programmingExerciseBuildConfigRepository, ExerciseService exerciseService) { - this.exerciseHintService = exerciseHintService; - this.exerciseHintRepository = exerciseHintRepository; this.versionControlService = versionControlService; this.programmingExerciseParticipationService = programmingExerciseParticipationService; this.programmingExerciseTestCaseRepository = programmingExerciseTestCaseRepository; @@ -101,7 +87,6 @@ public ProgrammingExerciseImportBasicService(ExerciseHintService exerciseHintSer this.submissionPolicyRepository = submissionPolicyRepository; this.programmingExerciseTaskRepository = programmingExerciseTaskRepository; this.programmingExerciseTaskService = programmingExerciseTaskService; - this.solutionEntryRepository = solutionEntryRepository; this.channelService = channelService; this.programmingExerciseBuildConfigRepository = programmingExerciseBuildConfigRepository; this.exerciseService = exerciseService; @@ -143,20 +128,16 @@ public ProgrammingExercise importProgrammingExerciseBasis(final ProgrammingExerc } // Hints, tasks, test cases and static code analysis categories - final Map newHintIdByOldId = exerciseHintService.copyExerciseHints(originalProgrammingExercise, newProgrammingExercise); - newProgrammingExercise.setBuildConfig(programmingExerciseBuildConfigRepository.save(newProgrammingExercise.getBuildConfig())); final ProgrammingExercise importedExercise = exerciseService.saveWithCompetencyLinks(newProgrammingExercise, programmingExerciseRepository::save); final Map newTestCaseIdByOldId = importTestCases(originalProgrammingExercise, importedExercise); - final Map newTaskIdByOldId = importTasks(originalProgrammingExercise, importedExercise, newTestCaseIdByOldId); - updateTaskExerciseHintReferences(originalProgrammingExercise, importedExercise, newTaskIdByOldId, newHintIdByOldId); + importTasks(originalProgrammingExercise, importedExercise, newTestCaseIdByOldId); // Set up new exercise submission policy before the solution entries are imported importSubmissionPolicy(importedExercise); // Having the submission policy in place prevents errors - importSolutionEntries(originalProgrammingExercise, importedExercise, newTestCaseIdByOldId, newHintIdByOldId); // Use the template problem statement (with ids) as a new basis (You cannot edit the problem statement while importing) // Then replace the old test ids by the newly created ones. @@ -284,33 +265,62 @@ private Map importTestCases(final ProgrammingExercise templateExerci } /** - * Copies tasks from one exercise to another. Because the tasks from the template exercise references its test cases, the - * references between tasks and test cases also need to be changed. + * Imports tasks from a template exercise to a new exercise. The tasks will get new IDs, thus being saved as a new entity. + * The remaining contents stay the same, especially the test cases. * - * @param templateExercise The template exercise which tasks should be copied - * @param targetExercise The new exercise to which all tasks should get copied to - * @param newTestCaseIdByOldId A map with the old test case id as a key and the new test case id as a value - * @return A map with the old task id as a key and the new task id as value + * @param sourceExercise The template exercise which tasks should get copied + * @param targetExercise The new exercise to which all tasks should get copied to + * @param testCaseIdMapping A map with the old test case id as a key and the new test case id as a value */ - private Map importTasks(final ProgrammingExercise templateExercise, final ProgrammingExercise targetExercise, Map newTestCaseIdByOldId) { - Map newIdByOldId = new HashMap<>(); - targetExercise.setTasks(templateExercise.getTasks().stream().map(task -> { - final var copy = new ProgrammingExerciseTask(); - - // copy everything except for the referenced exercise - copy.setTaskName(task.getTaskName()); - // change reference to newly imported test cases from the target exercise - copy.setTestCases(task.getTestCases().stream().map(testCase -> { - Long oldTestCaseId = testCase.getId(); - Long newTestCaseId = newTestCaseIdByOldId.get(oldTestCaseId); - return targetExercise.getTestCases().stream().filter(newTestCase -> Objects.equals(newTestCaseId, newTestCase.getId())).findFirst().orElseThrow(); - }).collect(Collectors.toSet())); - copy.setExercise(targetExercise); - programmingExerciseTaskRepository.save(copy); - newIdByOldId.put(task.getId(), copy.getId()); - return copy; - }).collect(Collectors.toCollection(ArrayList::new))); - return newIdByOldId; + private void importTasks(final ProgrammingExercise sourceExercise, final ProgrammingExercise targetExercise, Map testCaseIdMapping) { + // Map the tasks from the template exercise to new tasks in the target exercise + List newTasks = sourceExercise.getTasks().stream().map(templateTask -> createTaskCopy(templateTask, targetExercise, testCaseIdMapping)).toList(); + + // Set the new tasks to the target exercise + targetExercise.setTasks(new ArrayList<>(newTasks)); + } + + /** + * Creates a copy of a task from a template exercise and links it to the target exercise. The test cases of the task + * are also copied and linked to the new task. + * + * @param sourceTask The template task which should be copied + * @param targetExercise The new exercise to which the task should be linked + * @param testCaseIdMapping A map with the old test case id as a key and the new test case id as a value + * @return The new task + */ + private ProgrammingExerciseTask createTaskCopy(ProgrammingExerciseTask sourceTask, ProgrammingExercise targetExercise, Map testCaseIdMapping) { + ProgrammingExerciseTask copiedTask = new ProgrammingExerciseTask(); + + // Copy task properties + copiedTask.setTaskName(sourceTask.getTaskName()); + + // Map and set new test cases + Set mappedTestCases = sourceTask.getTestCases().stream().map(testCase -> findMappedTestCase(testCase, targetExercise, testCaseIdMapping)) + .collect(Collectors.toSet()); + copiedTask.setTestCases(mappedTestCases); + + // Link the task to the target exercise + copiedTask.setExercise(targetExercise); + + // Persist the new task + programmingExerciseTaskRepository.save(copiedTask); + return copiedTask; + } + + /** + * Finds a test case in the target exercise that corresponds to a test case in the template exercise. + * + * @param existingTestCase The test case from the template exercise + * @param targetExercise The new exercise to which the test case should be linked + * @param testCaseIdMapping A map with the old test case id as a key and the new test case id as a value + * @return The test case in the target exercise + */ + private ProgrammingExerciseTestCase findMappedTestCase(ProgrammingExerciseTestCase existingTestCase, ProgrammingExercise targetExercise, Map testCaseIdMapping) { + Long newTestCaseId = testCaseIdMapping.get(existingTestCase.getId()); + + return targetExercise.getTestCases().stream().filter(newTestCase -> Objects.equals(newTestCaseId, newTestCase.getId())).findFirst() + .orElseThrow(() -> new IllegalArgumentException("Test case not found for ID: " + newTestCaseId)); } /** @@ -380,68 +390,4 @@ else if (newExercise.isCourseExercise() && newExercise.getPlagiarismDetectionCon newExercise.setPlagiarismDetectionConfig(null); } } - - /** - * Updates the newly imported exercise hints to reference the newly imported tasks they belong to. - * - * @param templateExercise The template exercise which tasks should be copied - * @param targetExercise The new exercise to which all tasks should get copied to - * @param newTaskIdByOldId A map with the old task id as a key and the new task id as a value - * @param newHintIdByOldId A map with the old hint id as a key and the new hint id as a value - */ - private void updateTaskExerciseHintReferences(final ProgrammingExercise templateExercise, final ProgrammingExercise targetExercise, Map newTaskIdByOldId, - Map newHintIdByOldId) { - templateExercise.getExerciseHints().forEach(templateExerciseHint -> { - var templateTask = templateExerciseHint.getProgrammingExerciseTask(); - if (templateTask == null) { - return; - } - var targetTask = targetExercise.getTasks().stream().filter(newTask -> Objects.equals(newTask.getId(), newTaskIdByOldId.get(templateTask.getId()))).findAny() - .orElseThrow(); - var targetExerciseHint = targetExercise.getExerciseHints().stream() - .filter(newHint -> Objects.equals(newHint.getId(), newHintIdByOldId.get(templateExerciseHint.getId()))).findAny().orElseThrow(); - - targetExerciseHint.setProgrammingExerciseTask(targetTask); - exerciseHintRepository.save(targetExerciseHint); - targetTask.getExerciseHints().add(targetExerciseHint); - }); - } - - /** - * Copies solution entries from one exercise to another. Because the solution entries from the template exercise - * references its test cases and code hint, the references between them also need to be changed. - * - * @param templateExercise The template exercise which tasks should be copied - * @param targetExercise The new exercise to which all tasks should get copied to - * @param newTestCaseIdByOldId A map with the old test case id as a key and the new test case id as a value - * @param newHintIdByOldId A map with the old hint id as a key and the new hint id as a value - */ - private void importSolutionEntries(final ProgrammingExercise templateExercise, final ProgrammingExercise targetExercise, Map newTestCaseIdByOldId, - Map newHintIdByOldId) { - templateExercise.getTestCases().forEach(testCase -> { - var newSolutionEntries = solutionEntryRepository.findByTestCaseIdWithCodeHint(testCase.getId()).stream().map(solutionEntry -> { - Long newTestCaseId = newTestCaseIdByOldId.get(testCase.getId()); - var targetTestCase = targetExercise.getTestCases().stream().filter(newTestCase -> Objects.equals(newTestCaseId, newTestCase.getId())).findFirst().orElseThrow(); - - CodeHint codeHint = null; - if (solutionEntry.getCodeHint() != null) { - Long newHintId = newHintIdByOldId.get(solutionEntry.getCodeHint().getId()); - codeHint = (CodeHint) targetExercise.getExerciseHints().stream().filter(newHint -> Objects.equals(newHintId, newHint.getId())).findFirst().orElseThrow(); - } - var copy = new ProgrammingExerciseSolutionEntry(); - copy.setCode(solutionEntry.getCode()); - copy.setPreviousCode(solutionEntry.getPreviousCode()); - copy.setLine(solutionEntry.getLine()); - copy.setPreviousLine(solutionEntry.getPreviousLine()); - copy.setTestCase(targetTestCase); - targetTestCase.getSolutionEntries().add(copy); - copy.setCodeHint(codeHint); - if (codeHint != null) { - codeHint.getSolutionEntries().add(copy); - } - return copy; - }).collect(Collectors.toSet()); - solutionEntryRepository.saveAll(newSolutionEntries); - }); - } } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseImportService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseImportService.java index 710837ac4fb4..0c308cc80c0a 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseImportService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseImportService.java @@ -35,7 +35,6 @@ import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseTestCaseRepository; import de.tum.cit.aet.artemis.programming.service.ci.ContinuousIntegrationService; import de.tum.cit.aet.artemis.programming.service.ci.ContinuousIntegrationTriggerService; -import de.tum.cit.aet.artemis.programming.service.hestia.ProgrammingExerciseTaskService; import de.tum.cit.aet.artemis.programming.service.vcs.VersionControlService; @Profile(PROFILE_CORE) diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseRepositoryService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseRepositoryService.java index 43523ec566f1..ffbf6aa37f0a 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseRepositoryService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseRepositoryService.java @@ -404,8 +404,6 @@ private void setupJVMTestTemplateAndPush(final RepositoryResources resources, fi final Map sectionsMap = new HashMap<>(); // Keep or delete static code analysis configuration in the build configuration file sectionsMap.put("static-code-analysis", Boolean.TRUE.equals(programmingExercise.isStaticCodeAnalysisEnabled())); - // Keep or delete testwise coverage configuration in the build file - sectionsMap.put("record-testwise-coverage", Boolean.TRUE.equals(programmingExercise.getBuildConfig().isTestwiseCoverageEnabled())); if (programmingExercise.getBuildConfig().hasSequentialTestRuns()) { setupTestTemplateSequentialTestRuns(resources, templatePath, projectTemplatePath, projectType, sectionsMap); diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseService.java index d85af7904c4c..d03c14a5cbc5 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseService.java @@ -17,7 +17,6 @@ import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -26,7 +25,6 @@ import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.Collectors; import jakarta.annotation.Nullable; @@ -70,7 +68,7 @@ import de.tum.cit.aet.artemis.programming.domain.AuxiliaryRepository; import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise; import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseBuildConfig; -import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseTestCase; +import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseTask; import de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage; import de.tum.cit.aet.artemis.programming.domain.ProjectType; import de.tum.cit.aet.artemis.programming.domain.Repository; @@ -78,23 +76,19 @@ import de.tum.cit.aet.artemis.programming.domain.SolutionProgrammingExerciseParticipation; import de.tum.cit.aet.artemis.programming.domain.TemplateProgrammingExerciseParticipation; import de.tum.cit.aet.artemis.programming.domain.VcsRepositoryUri; -import de.tum.cit.aet.artemis.programming.domain.hestia.ProgrammingExerciseSolutionEntry; -import de.tum.cit.aet.artemis.programming.domain.hestia.ProgrammingExerciseTask; +import de.tum.cit.aet.artemis.programming.dto.aeolus.Windfile; import de.tum.cit.aet.artemis.programming.repository.AuxiliaryRepositoryRepository; import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseBuildConfigRepository; +import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseGitDiffReportRepository; import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseRepository; import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseStudentParticipationRepository; +import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseTaskRepository; import de.tum.cit.aet.artemis.programming.repository.SolutionProgrammingExerciseParticipationRepository; import de.tum.cit.aet.artemis.programming.repository.TemplateProgrammingExerciseParticipationRepository; -import de.tum.cit.aet.artemis.programming.repository.hestia.ProgrammingExerciseGitDiffReportRepository; -import de.tum.cit.aet.artemis.programming.repository.hestia.ProgrammingExerciseSolutionEntryRepository; -import de.tum.cit.aet.artemis.programming.repository.hestia.ProgrammingExerciseTaskRepository; import de.tum.cit.aet.artemis.programming.service.aeolus.AeolusTemplateService; -import de.tum.cit.aet.artemis.programming.service.aeolus.Windfile; import de.tum.cit.aet.artemis.programming.service.ci.CIPermission; import de.tum.cit.aet.artemis.programming.service.ci.ContinuousIntegrationService; import de.tum.cit.aet.artemis.programming.service.ci.ContinuousIntegrationTriggerService; -import de.tum.cit.aet.artemis.programming.service.hestia.ProgrammingExerciseTaskService; import de.tum.cit.aet.artemis.programming.service.structureoraclegenerator.OracleGenerator; import de.tum.cit.aet.artemis.programming.service.vcs.VersionControlService; @@ -163,8 +157,6 @@ public class ProgrammingExerciseService { private final ProgrammingExerciseTaskRepository programmingExerciseTaskRepository; - private final ProgrammingExerciseSolutionEntryRepository programmingExerciseSolutionEntryRepository; - private final ProgrammingExerciseTaskService programmingExerciseTaskService; private final ProgrammingExerciseGitDiffReportRepository programmingExerciseGitDiffReportRepository; @@ -206,11 +198,11 @@ public ProgrammingExerciseService(ProgrammingExerciseRepository programmingExerc ParticipationRepository participationRepository, ResultRepository resultRepository, UserRepository userRepository, GroupNotificationScheduleService groupNotificationScheduleService, InstanceMessageSendService instanceMessageSendService, AuxiliaryRepositoryRepository auxiliaryRepositoryRepository, ProgrammingExerciseTaskRepository programmingExerciseTaskRepository, - ProgrammingExerciseSolutionEntryRepository programmingExerciseSolutionEntryRepository, ProgrammingExerciseTaskService programmingExerciseTaskService, - ProgrammingExerciseGitDiffReportRepository programmingExerciseGitDiffReportRepository, ExerciseSpecificationService exerciseSpecificationService, - ProgrammingExerciseRepositoryService programmingExerciseRepositoryService, AuxiliaryRepositoryService auxiliaryRepositoryService, - SubmissionPolicyService submissionPolicyService, Optional programmingLanguageFeatureService, ChannelService channelService, - ProgrammingSubmissionService programmingSubmissionService, Optional irisSettingsService, Optional aeolusTemplateService, + ProgrammingExerciseTaskService programmingExerciseTaskService, ProgrammingExerciseGitDiffReportRepository programmingExerciseGitDiffReportRepository, + ExerciseSpecificationService exerciseSpecificationService, ProgrammingExerciseRepositoryService programmingExerciseRepositoryService, + AuxiliaryRepositoryService auxiliaryRepositoryService, SubmissionPolicyService submissionPolicyService, + Optional programmingLanguageFeatureService, ChannelService channelService, ProgrammingSubmissionService programmingSubmissionService, + Optional irisSettingsService, Optional aeolusTemplateService, Optional buildScriptGenerationService, ProgrammingExerciseStudentParticipationRepository programmingExerciseStudentParticipationRepository, ProfileService profileService, ExerciseService exerciseService, ProgrammingExerciseBuildConfigRepository programmingExerciseBuildConfigRepository, CompetencyProgressApi competencyProgressApi, @@ -230,7 +222,6 @@ public ProgrammingExerciseService(ProgrammingExerciseRepository programmingExerc this.instanceMessageSendService = instanceMessageSendService; this.auxiliaryRepositoryRepository = auxiliaryRepositoryRepository; this.programmingExerciseTaskRepository = programmingExerciseTaskRepository; - this.programmingExerciseSolutionEntryRepository = programmingExerciseSolutionEntryRepository; this.programmingExerciseTaskService = programmingExerciseTaskService; this.programmingExerciseGitDiffReportRepository = programmingExerciseGitDiffReportRepository; this.exerciseSpecificationService = exerciseSpecificationService; @@ -405,11 +396,6 @@ public void validateNewProgrammingExerciseSettings(ProgrammingExercise programmi throw new BadRequestAlertException("Checkout solution repository is not supported for this programming language", "Exercise", "checkoutSolutionRepositoryNotSupported"); } - // Check if testwise coverage analysis is enabled - if (Boolean.TRUE.equals(buildConfig.isTestwiseCoverageEnabled()) && !programmingLanguageFeature.testwiseCoverageAnalysisSupported()) { - throw new BadRequestAlertException("Testwise coverage analysis is not supported for this language", "Exercise", "testwiseCoverageAnalysisNotSupported"); - } - programmingExerciseRepository.validateCourseSettings(programmingExercise, course); validateStaticCodeAnalysisSettings(programmingExercise); @@ -1033,12 +1019,9 @@ public boolean preCheckProjectExistsOnVCSOrCI(ProgrammingExercise programmingExe * * @param exerciseId of the exercise */ - public void deleteTasksWithSolutionEntries(Long exerciseId) { - List tasks = programmingExerciseTaskRepository.findByExerciseIdWithTestCaseAndSolutionEntriesElseThrow(exerciseId); - Set solutionEntries = tasks.stream().map(ProgrammingExerciseTask::getTestCases).flatMap(Collection::stream) - .map(ProgrammingExerciseTestCase::getSolutionEntries).flatMap(Collection::stream).collect(Collectors.toSet()); + public void deleteTasks(Long exerciseId) { + List tasks = programmingExerciseTaskRepository.findByExerciseIdWithTestCaseElseThrow(exerciseId); programmingExerciseTaskRepository.deleteAll(tasks); - programmingExerciseSolutionEntryRepository.deleteAll(solutionEntries); } private void resetAllStudentBuildPlanIdsForExercise(ProgrammingExercise programmingExercise) { diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/ProgrammingExerciseTaskService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseTaskService.java similarity index 91% rename from src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/ProgrammingExerciseTaskService.java rename to src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseTaskService.java index 0b1a14be8646..3ec052802f83 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/ProgrammingExerciseTaskService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseTaskService.java @@ -1,4 +1,4 @@ -package de.tum.cit.aet.artemis.programming.service.hestia; +package de.tum.cit.aet.artemis.programming.service; import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; @@ -20,11 +20,10 @@ import org.springframework.stereotype.Service; import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise; +import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseTask; import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseTestCase; -import de.tum.cit.aet.artemis.programming.domain.hestia.ProgrammingExerciseTask; +import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseTaskRepository; import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseTestCaseRepository; -import de.tum.cit.aet.artemis.programming.repository.hestia.ExerciseHintRepository; -import de.tum.cit.aet.artemis.programming.repository.hestia.ProgrammingExerciseTaskRepository; @Profile(PROFILE_CORE) @Service @@ -34,8 +33,6 @@ public class ProgrammingExerciseTaskService { private final ProgrammingExerciseTestCaseRepository programmingExerciseTestCaseRepository; - private final ExerciseHintRepository exerciseHintRepository; - /** * Pattern that is used to extract the tasks (capturing group {@code name}) and test case names (capturing groups {@code tests}) from the problem statement. * Example: "[task][Implement BubbleSort](testBubbleSort,testBubbleSortHidden)". Following groups are extracted by the capturing groups: @@ -85,21 +82,18 @@ public class ProgrammingExerciseTaskService { private static final Pattern TESTID_PATTERN = Pattern.compile(TESTID_START + "(\\d+)" + TESTID_END); public ProgrammingExerciseTaskService(ProgrammingExerciseTaskRepository programmingExerciseTaskRepository, - ProgrammingExerciseTestCaseRepository programmingExerciseTestCaseRepository, ExerciseHintRepository exerciseHintRepository) { + ProgrammingExerciseTestCaseRepository programmingExerciseTestCaseRepository) { this.programmingExerciseTaskRepository = programmingExerciseTaskRepository; this.programmingExerciseTestCaseRepository = programmingExerciseTestCaseRepository; - this.exerciseHintRepository = exerciseHintRepository; } /** - * Deletes a ProgrammingExerciseTask together with its CodeHints + * Deletes a ProgrammingExerciseTask * This has to be manually done, as there is no orphanRemoval between the two entities * * @param task The task to delete */ public void delete(ProgrammingExerciseTask task) { - var exerciseHints = exerciseHintRepository.findByTaskId(task.getId()); - exerciseHintRepository.deleteAll(exerciseHints); programmingExerciseTaskRepository.delete(task); } @@ -109,14 +103,13 @@ public void delete(ProgrammingExerciseTask task) { * If there is already a task with the same test cases as a new one, but with a different name the existing one will be renamed. * * @param exercise The programming exercise to extract the tasks from - * @return The current tasks of the exercise */ - public Set updateTasksFromProblemStatement(ProgrammingExercise exercise) { + public void updateTasksFromProblemStatement(ProgrammingExercise exercise) { var previousTasks = programmingExerciseTaskRepository.findByExerciseIdWithTestCases(exercise.getId()); var extractedTasks = new HashSet<>(extractTasks(exercise)); // No changes if (previousTasks.equals(extractedTasks)) { - return previousTasks; + return; } // Add all tasks that did not change var tasksToBeSaved = new HashSet<>(previousTasks); @@ -151,24 +144,7 @@ public Set updateTasksFromProblemStatement(ProgrammingE for (ProgrammingExerciseTask task : tasksToBeSaved) { task.setExercise(exercise); } - return new HashSet<>(programmingExerciseTaskRepository.saveAll(tasksToBeSaved)); - } - - /** - * Gets the tasks of a programming exercise sorted by their order in the problem statement - * TODO: Replace this with an @OrderColumn on tasks in ProgrammingExercise - * - * @param exercise The programming exercise - * @return The sorted tasks - */ - public List getSortedTasks(ProgrammingExercise exercise) { - var unsortedTasks = programmingExerciseTaskRepository.findByExerciseIdWithTestCases(exercise.getId()); - var sortedExtractedTasks = extractTasks(exercise); - return sortedExtractedTasks.stream() - .map(extractedTask -> unsortedTasks.stream() - .filter(task -> task.getTaskName().equals(extractedTask.getTaskName()) && task.getTestCases().equals(extractedTask.getTestCases())).findFirst() - .orElse(null)) - .distinct().filter(Objects::nonNull).toList(); + programmingExerciseTaskRepository.saveAll(tasksToBeSaved); } /** @@ -178,7 +154,7 @@ public List getSortedTasks(ProgrammingExercise exercise * @return Set of all tasks and its test cases */ public Set getTasksWithoutInactiveTestCases(long exerciseId) { - return programmingExerciseTaskRepository.findByExerciseIdWithTestCaseAndSolutionEntriesElseThrow(exerciseId).stream() + return programmingExerciseTaskRepository.findByExerciseIdWithTestCaseElseThrow(exerciseId).stream() .peek(task -> task.getTestCases().removeIf(Predicate.not(ProgrammingExerciseTestCase::isActive))).collect(Collectors.toSet()); } @@ -187,10 +163,10 @@ public Set getTasksWithoutInactiveTestCases(long exerci * Additionally, adds a new task for all test cases with no manually assigned task and adds all tests to that task * * @param exerciseId of the programming exercise - * @return Set of all tasks including one for not manually assigned tests + * @return List of all tasks including one for not manually assigned tests */ public List getTasksWithUnassignedTestCases(long exerciseId) { - List tasks = programmingExerciseTaskRepository.findByExerciseIdWithTestCaseAndSolutionEntriesElseThrow(exerciseId); + List tasks = programmingExerciseTaskRepository.findByExerciseIdWithTestCaseElseThrow(exerciseId); Set testsWithTasks = tasks.stream().flatMap(task -> task.getTestCases().stream()).collect(Collectors.toSet()); diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseTestCaseService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseTestCaseService.java index f3770b417e5b..f06a901b8479 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseTestCaseService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseTestCaseService.java @@ -27,7 +27,6 @@ import de.tum.cit.aet.artemis.programming.dto.ProgrammingExerciseTestCaseDTO; import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseRepository; import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseTestCaseRepository; -import de.tum.cit.aet.artemis.programming.service.hestia.ProgrammingExerciseTaskService; @Profile(PROFILE_CORE) @Service diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingLanguageFeature.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingLanguageFeature.java index 88bee465e155..df07a8f6d5b3 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingLanguageFeature.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingLanguageFeature.java @@ -13,6 +13,5 @@ */ @JsonInclude(JsonInclude.Include.NON_EMPTY) public record ProgrammingLanguageFeature(ProgrammingLanguage programmingLanguage, boolean sequentialTestRuns, boolean staticCodeAnalysis, boolean plagiarismCheckSupported, - boolean packageNameRequired, boolean checkoutSolutionRepositoryAllowed, List projectTypes, boolean testwiseCoverageAnalysisSupported, - boolean auxiliaryRepositoriesSupported) { + boolean packageNameRequired, boolean checkoutSolutionRepositoryAllowed, List projectTypes, boolean auxiliaryRepositoriesSupported) { } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingMessagingService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingMessagingService.java index 349aa53bc89e..7a7861cbcbcc 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingMessagingService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingMessagingService.java @@ -28,6 +28,7 @@ import de.tum.cit.aet.artemis.exercise.dto.SubmissionDTO; import de.tum.cit.aet.artemis.exercise.repository.ParticipationRepository; import de.tum.cit.aet.artemis.exercise.repository.TeamRepository; +import de.tum.cit.aet.artemis.iris.repository.IrisExerciseSettingsRepository; import de.tum.cit.aet.artemis.iris.service.pyris.PyrisEventService; import de.tum.cit.aet.artemis.iris.service.pyris.event.NewResultEvent; import de.tum.cit.aet.artemis.lti.service.LtiNewResultService; @@ -57,16 +58,20 @@ public class ProgrammingMessagingService { private final Optional pyrisEventService; + private final Optional irisExerciseSettingsRepository; + private final ParticipationRepository participationRepository; public ProgrammingMessagingService(GroupNotificationService groupNotificationService, WebsocketMessagingService websocketMessagingService, ResultWebsocketService resultWebsocketService, Optional ltiNewResultService, TeamRepository teamRepository, - Optional pyrisEventService, ParticipationRepository participationRepository) { + Optional pyrisEventService, Optional irisExerciseSettingsRepository, + ParticipationRepository participationRepository) { this.groupNotificationService = groupNotificationService; this.websocketMessagingService = websocketMessagingService; this.resultWebsocketService = resultWebsocketService; this.ltiNewResultService = ltiNewResultService; this.teamRepository = teamRepository; + this.irisExerciseSettingsRepository = irisExerciseSettingsRepository; this.participationRepository = participationRepository; this.pyrisEventService = pyrisEventService; } @@ -188,13 +193,16 @@ public void notifyUserAboutNewResult(Result result, ProgrammingExerciseParticipa if (participation instanceof ProgrammingExerciseStudentParticipation studentParticipation) { // do not try to report results for template or solution participations ltiNewResultService.ifPresent(newResultService -> newResultService.onNewResult(studentParticipation)); - // Inform Iris about the submission status + // Inform Iris about the submission status (when certain conditions are met) notifyIrisAboutSubmissionStatus(result, studentParticipation); } } /** * Notify Iris about the submission status for the given result and student participation. + * Only notifies if the user has accepted Iris, the exercise is not an exam exercise, and the exercise chat is enabled in the exercise settings + * NOTE: we check those settings early to prevent unnecessary database queries and exceptions later on in most cases. More sophisticated checks are done in the Iris service. + *

* If the submission was successful, Iris will be informed about the successful submission. * If the submission failed, Iris will be informed about the submission failure. * Iris will only be informed about the submission status if the participant is a user. @@ -203,14 +211,18 @@ public void notifyUserAboutNewResult(Result result, ProgrammingExerciseParticipa * @param studentParticipation the student participation for which Iris should be informed about the submission status */ private void notifyIrisAboutSubmissionStatus(Result result, ProgrammingExerciseStudentParticipation studentParticipation) { - if (studentParticipation.getParticipant() instanceof User) { + if (studentParticipation.getParticipant() instanceof User user) { pyrisEventService.ifPresent(eventService -> { - // Inform event service about the new result - try { - eventService.trigger(new NewResultEvent(result)); - } - catch (Exception e) { - log.error("Could not trigger service for result {}", result.getId(), e); + final var exercise = studentParticipation.getExercise(); + if (user.hasAcceptedIris() && !exercise.isExamExercise() && irisExerciseSettingsRepository.get().isExerciseChatEnabled(exercise.getId())) { + // Inform event service about the new result + try { + // This is done asynchronously to prevent blocking the current thread + eventService.trigger(new NewResultEvent(result)); + } + catch (Exception e) { + log.error("Could not trigger service for result {}", result.getId(), e); + } } }); } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingSubmissionService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingSubmissionService.java index 9de4485f16b2..e1ae83935eec 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingSubmissionService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingSubmissionService.java @@ -67,7 +67,6 @@ import de.tum.cit.aet.artemis.programming.repository.ProgrammingSubmissionRepository; import de.tum.cit.aet.artemis.programming.repository.SubmissionPolicyRepository; import de.tum.cit.aet.artemis.programming.service.ci.ContinuousIntegrationTriggerService; -import de.tum.cit.aet.artemis.programming.service.hestia.ProgrammingExerciseGitDiffReportService; import de.tum.cit.aet.artemis.programming.service.vcs.VersionControlService; // TODO: this class has too many dependencies to other services. We should reduce this diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/aeolus/Action.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/aeolus/Action.java deleted file mode 100644 index be22f81ddad9..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/aeolus/Action.java +++ /dev/null @@ -1,86 +0,0 @@ -package de.tum.cit.aet.artemis.programming.service.aeolus; - -import java.util.List; -import java.util.Map; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; - -/** - * Base class for the actions that can be defined in a {@link Windfile} - */ -// TODO: remove and convert subclasses into Records -@JsonIgnoreProperties(ignoreUnknown = true) -@JsonInclude(JsonInclude.Include.NON_EMPTY) -public abstract class Action { - - private String name; - - private Map parameters; - - private Map environment; - - private List results; - - private String workdir; - - private boolean runAlways; - - private String platform; - - public Map getParameters() { - return parameters; - } - - public void setParameters(Map parameters) { - this.parameters = parameters; - } - - public Map getEnvironment() { - return environment; - } - - public void setEnvironment(Map environment) { - this.environment = environment; - } - - public boolean isRunAlways() { - return runAlways; - } - - public void setRunAlways(boolean runAlways) { - this.runAlways = runAlways; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public List getResults() { - return results; - } - - public void setResults(List results) { - this.results = results; - } - - public String getWorkdir() { - return workdir; - } - - public void setWorkdir(String workdir) { - this.workdir = workdir; - } - - public String getPlatform() { - return platform; - } - - public void setPlatform(String platform) { - this.platform = platform; - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/aeolus/ActionDeserializer.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/aeolus/ActionDeserializer.java index 65d42487e398..32463ba0d841 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/aeolus/ActionDeserializer.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/aeolus/ActionDeserializer.java @@ -8,6 +8,10 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import de.tum.cit.aet.artemis.programming.dto.aeolus.Action; +import de.tum.cit.aet.artemis.programming.dto.aeolus.PlatformAction; +import de.tum.cit.aet.artemis.programming.dto.aeolus.ScriptAction; + /** * Deserializer for {@link Action} that determines the type of the action based on the content of the JSON. */ diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/aeolus/AeolusBuildPlanService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/aeolus/AeolusBuildPlanService.java index 9cb410de37fa..9dd35b363709 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/aeolus/AeolusBuildPlanService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/aeolus/AeolusBuildPlanService.java @@ -35,6 +35,8 @@ import de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage; import de.tum.cit.aet.artemis.programming.domain.VcsRepositoryUri; import de.tum.cit.aet.artemis.programming.dto.AeolusGenerationResponseDTO; +import de.tum.cit.aet.artemis.programming.dto.aeolus.AeolusRepository; +import de.tum.cit.aet.artemis.programming.dto.aeolus.Windfile; import de.tum.cit.aet.artemis.programming.service.InternalUrlService; import de.tum.cit.aet.artemis.programming.service.ci.ContinuousIntegrationService; diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/aeolus/AeolusBuildScriptGenerationService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/aeolus/AeolusBuildScriptGenerationService.java index 8830adb96389..57141ab1a84a 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/aeolus/AeolusBuildScriptGenerationService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/aeolus/AeolusBuildScriptGenerationService.java @@ -10,6 +10,8 @@ import de.tum.cit.aet.artemis.core.service.ProfileService; import de.tum.cit.aet.artemis.programming.domain.AeolusTarget; import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise; +import de.tum.cit.aet.artemis.programming.dto.aeolus.Windfile; +import de.tum.cit.aet.artemis.programming.dto.aeolus.WindfileMetadata; import de.tum.cit.aet.artemis.programming.service.BuildScriptGenerationService; import de.tum.cit.aet.artemis.programming.service.BuildScriptProviderService; @@ -52,12 +54,12 @@ public String getScript(ProgrammingExercise programmingExercise) throws JsonProc windfile = aeolusTemplateService.getDefaultWindfileFor(programmingExercise); } if (windfile != null) { - WindfileMetadata oldMetadata = windfile.getMetadata(); + WindfileMetadata oldMetadata = windfile.metadata(); // Creating a new instance of WindfileMetadata with placeholder values for id, name, and description, // and copying the rest of the fields from oldMetadata WindfileMetadata updatedMetadata = new WindfileMetadata("not-used", "not-used", "not-used", oldMetadata.author(), oldMetadata.gitCredentials(), oldMetadata.docker(), oldMetadata.resultHook(), oldMetadata.resultHookCredentials()); - windfile.setMetadata(updatedMetadata); + windfile = new Windfile(windfile, updatedMetadata); return aeolusBuildPlanService.generateBuildScript(windfile, AeolusTarget.CLI); } return null; diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/aeolus/AeolusTemplateService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/aeolus/AeolusTemplateService.java index 64321ad3b61d..f2e51ab78496 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/aeolus/AeolusTemplateService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/aeolus/AeolusTemplateService.java @@ -26,6 +26,10 @@ import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseBuildConfig; import de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage; import de.tum.cit.aet.artemis.programming.domain.ProjectType; +import de.tum.cit.aet.artemis.programming.dto.aeolus.Action; +import de.tum.cit.aet.artemis.programming.dto.aeolus.DockerConfig; +import de.tum.cit.aet.artemis.programming.dto.aeolus.Windfile; +import de.tum.cit.aet.artemis.programming.dto.aeolus.WindfileMetadata; import de.tum.cit.aet.artemis.programming.service.BuildScriptProviderService; import de.tum.cit.aet.artemis.programming.web.localci.AeolusTemplateResource; @@ -84,7 +88,7 @@ public void cacheOnBoot() { script = buildScriptProviderService.replacePlaceholders(script, null, null, null); } Windfile windfile = readWindfile(script); - this.addInstanceVariablesToWindfile(windfile, ProgrammingLanguage.valueOf(directory.toUpperCase()), optionalProjectType); + windfile = addInstanceVariablesToWindfile(windfile, ProgrammingLanguage.valueOf(directory.toUpperCase()), optionalProjectType); templateCache.put(uniqueKey, windfile); } catch (IOException | IllegalArgumentException e) { @@ -127,17 +131,15 @@ private static Windfile readWindfile(String yaml) throws IOException { * @param projectType the project type for which the template file should be returned. If omitted, a default depending on the language will be used. * @param staticAnalysis whether the static analysis template should be used * @param sequentialRuns whether the sequential runs template should be used - * @param testCoverage whether the test coverage template should be used * @return the requested template as a {@link Windfile} object * @throws IOException if the file does not exist */ - public Windfile getWindfileFor(ProgrammingLanguage programmingLanguage, Optional projectType, Boolean staticAnalysis, Boolean sequentialRuns, Boolean testCoverage) - throws IOException { + public Windfile getWindfileFor(ProgrammingLanguage programmingLanguage, Optional projectType, Boolean staticAnalysis, Boolean sequentialRuns) throws IOException { if (programmingLanguage.equals(ProgrammingLanguage.JAVA) && projectType.isEmpty()) { // to be backwards compatible, we assume that java exercises without project type are plain maven projects projectType = Optional.of(ProjectType.PLAIN_MAVEN); } - String templateFileName = buildScriptProviderService.buildTemplateName(projectType, staticAnalysis, sequentialRuns, testCoverage, "yaml"); + String templateFileName = buildScriptProviderService.buildTemplateName(projectType, staticAnalysis, sequentialRuns, "yaml"); String uniqueKey = programmingLanguage.name().toLowerCase() + "_" + templateFileName; if (templateCache.containsKey(uniqueKey)) { return templateCache.get(uniqueKey); @@ -151,7 +153,7 @@ public Windfile getWindfileFor(ProgrammingLanguage programmingLanguage, Optional scriptCache = buildScriptProviderService.replacePlaceholders(scriptCache, null, null, null); } Windfile windfile = readWindfile(scriptCache); - this.addInstanceVariablesToWindfile(windfile, programmingLanguage, projectType); + windfile = addInstanceVariablesToWindfile(windfile, programmingLanguage, projectType); templateCache.put(uniqueKey, windfile); return windfile; } @@ -184,7 +186,7 @@ public Windfile getDefaultWindfileFor(ProgrammingExercise exercise) { try { ProgrammingExerciseBuildConfig buildConfig = exercise.getBuildConfig(); return getWindfileFor(exercise.getProgrammingLanguage(), Optional.ofNullable(exercise.getProjectType()), exercise.isStaticCodeAnalysisEnabled(), - buildConfig.hasSequentialTestRuns(), buildConfig.isTestwiseCoverageEnabled()); + buildConfig.hasSequentialTestRuns()); } catch (IOException e) { log.info("No windfile for the settings of exercise {}", exercise.getId(), e); @@ -202,23 +204,24 @@ public Windfile getDefaultWindfileFor(ProgrammingExercise exercise) { * @param windfile the Windfile template to be updated with Docker configuration * @param language the programming language used, which determines the Docker image and flags * @param projectType an optional specifying the project type; influences the Docker configuration + * @return the updated Windfile instance with Docker configuration */ - private void addInstanceVariablesToWindfile(Windfile windfile, ProgrammingLanguage language, Optional projectType) { + private Windfile addInstanceVariablesToWindfile(Windfile windfile, ProgrammingLanguage language, Optional projectType) { - WindfileMetadata metadata = windfile.getMetadata(); + WindfileMetadata metadata = windfile.metadata(); if (metadata == null) { metadata = new WindfileMetadata(); } if (projectType.isPresent() && ProjectType.XCODE.equals(projectType.get())) { // xcode does not support docker metadata = new WindfileMetadata(); - windfile.setMetadata(metadata); - return; } - String image = programmingLanguageConfiguration.getImage(language, projectType); - DockerConfig dockerConfig = new DockerConfig(image, null, null, programmingLanguageConfiguration.getDefaultDockerFlags()); - metadata = new WindfileMetadata(metadata.name(), metadata.id(), metadata.description(), metadata.author(), metadata.gitCredentials(), dockerConfig, metadata.resultHook(), - metadata.resultHookCredentials()); - windfile.setMetadata(metadata); + else { + String image = programmingLanguageConfiguration.getImage(language, projectType); + DockerConfig dockerConfig = new DockerConfig(image, null, null, programmingLanguageConfiguration.getDefaultDockerFlags()); + metadata = new WindfileMetadata(metadata.name(), metadata.id(), metadata.description(), metadata.author(), metadata.gitCredentials(), dockerConfig, + metadata.resultHook(), metadata.resultHookCredentials()); + } + return new Windfile(windfile, metadata); } } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/aeolus/PlatformAction.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/aeolus/PlatformAction.java deleted file mode 100644 index 5c161ffe798b..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/aeolus/PlatformAction.java +++ /dev/null @@ -1,33 +0,0 @@ -package de.tum.cit.aet.artemis.programming.service.aeolus; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; - -/** - * Represents a CI action that is intended to run only on a specific target, can be used in a {@link Windfile}. - */ -// TODO: convert into Record -@JsonIgnoreProperties(ignoreUnknown = true) -@JsonInclude(JsonInclude.Include.NON_EMPTY) -public class PlatformAction extends Action { - - private String kind; - - private String type; - - public String getKind() { - return kind; - } - - public void setKind(String kind) { - this.kind = kind; - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/aeolus/Windfile.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/aeolus/Windfile.java deleted file mode 100644 index a9f2382591d4..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/aeolus/Windfile.java +++ /dev/null @@ -1,123 +0,0 @@ -package de.tum.cit.aet.artemis.programming.service.aeolus; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.module.SimpleModule; - -/** - * Represents a windfile, the definition file for an aeolus build plan that - * can then be used to generate a Jenkinsfile. - */ -// TODO convert into Record -@JsonIgnoreProperties(ignoreUnknown = true) -@JsonInclude(JsonInclude.Include.NON_EMPTY) -public class Windfile { - - private static final ObjectMapper mapper = new ObjectMapper(); - - private String api; - - private WindfileMetadata metadata; - - private List actions = new ArrayList<>(); - - private Map repositories = new HashMap<>(); - - public String getApi() { - return api; - } - - public void setApi(String api) { - this.api = api; - } - - public WindfileMetadata getMetadata() { - return metadata; - } - - public void setMetadata(WindfileMetadata metadata) { - this.metadata = metadata; - } - - public List getActions() { - return actions; - } - - public void setActions(List actions) { - this.actions = actions; - } - - /** - * Gets the script actions of a windfile. - * - * @return the script actions of a windfile. - */ - public List getScriptActions() { - List scriptActions = new ArrayList<>(); - for (Action action : actions) { - if (action instanceof ScriptAction) { - scriptActions.add((ScriptAction) action); - } - } - return scriptActions; - } - - public void setRepositories(Map repositories) { - this.repositories = repositories; - } - - public Map getRepositories() { - return repositories; - } - - /** - * Sets the pre-processing metadata for the windfile. - * - * @param id the id of the windfile. - * @param name the name of the windfile. - * @param gitCredentials the git credentials of the windfile. - * @param resultHook the result hook of the windfile. - * @param description the description of the windfile. - * @param repositories the repositories of the windfile. - * @param resultHookCredentials the credentials for the result hook of the windfile. - */ - public void setPreProcessingMetadata(String id, String name, String gitCredentials, String resultHook, String description, Map repositories, - String resultHookCredentials) { - this.setMetadata(new WindfileMetadata(name, id, description, null, gitCredentials, null, resultHook, resultHookCredentials)); - this.setRepositories(repositories); - } - - /** - * Deserializes a windfile from a json string. - * - * @param json the json string to deserialize. - * @return the deserialized windfile. - * @throws JsonProcessingException if the json string is not valid. - */ - public static Windfile deserialize(String json) throws JsonProcessingException { - SimpleModule module = new SimpleModule(); - module.addDeserializer(Action.class, new ActionDeserializer()); - mapper.registerModule(module); - return mapper.readValue(json, Windfile.class); - } - - /** - * Collects the results of all actions of a windfile. - * - * @return the results of all actions of this windfile - */ - public List getResults() { - List results = new ArrayList<>(); - for (Action action : actions.stream().filter(action -> action.getResults() != null && !action.getResults().isEmpty()).toList()) { - results.addAll(action.getResults()); - } - return results; - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/ci/AbstractContinuousIntegrationResultService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/ci/AbstractContinuousIntegrationResultService.java index 238d4ffca0fc..2f540cd869b7 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/ci/AbstractContinuousIntegrationResultService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/ci/AbstractContinuousIntegrationResultService.java @@ -11,13 +11,12 @@ import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise; import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseParticipation; import de.tum.cit.aet.artemis.programming.domain.build.BuildLogEntry; -import de.tum.cit.aet.artemis.programming.dto.AbstractBuildResultNotificationDTO; -import de.tum.cit.aet.artemis.programming.dto.BuildJobDTOInterface; +import de.tum.cit.aet.artemis.programming.dto.BuildJobInterface; +import de.tum.cit.aet.artemis.programming.dto.BuildResultNotification; import de.tum.cit.aet.artemis.programming.repository.BuildLogStatisticsEntryRepository; import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseBuildConfigRepository; import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseTestCaseRepository; import de.tum.cit.aet.artemis.programming.service.ProgrammingExerciseFeedbackCreationService; -import de.tum.cit.aet.artemis.programming.service.hestia.TestwiseCoverageService; public abstract class AbstractContinuousIntegrationResultService implements ContinuousIntegrationResultService { @@ -25,32 +24,29 @@ public abstract class AbstractContinuousIntegrationResultService implements Cont protected final BuildLogStatisticsEntryRepository buildLogStatisticsEntryRepository; - protected final TestwiseCoverageService testwiseCoverageService; - protected final ProgrammingExerciseFeedbackCreationService feedbackCreationService; protected final ProgrammingExerciseBuildConfigRepository programmingExerciseBuildConfigRepository; protected AbstractContinuousIntegrationResultService(ProgrammingExerciseTestCaseRepository testCaseRepository, - BuildLogStatisticsEntryRepository buildLogStatisticsEntryRepository, TestwiseCoverageService testwiseCoverageService, - ProgrammingExerciseFeedbackCreationService feedbackCreationService, ProgrammingExerciseBuildConfigRepository programmingExerciseBuildConfigRepository) { + BuildLogStatisticsEntryRepository buildLogStatisticsEntryRepository, ProgrammingExerciseFeedbackCreationService feedbackCreationService, + ProgrammingExerciseBuildConfigRepository programmingExerciseBuildConfigRepository) { this.testCaseRepository = testCaseRepository; this.buildLogStatisticsEntryRepository = buildLogStatisticsEntryRepository; - this.testwiseCoverageService = testwiseCoverageService; this.feedbackCreationService = feedbackCreationService; this.programmingExerciseBuildConfigRepository = programmingExerciseBuildConfigRepository; } @Override - public Result createResultFromBuildResult(AbstractBuildResultNotificationDTO buildResult, ProgrammingExerciseParticipation participation) { + public Result createResultFromBuildResult(BuildResultNotification buildResult, ProgrammingExerciseParticipation participation) { ProgrammingExercise exercise = participation.getProgrammingExercise(); final var result = new Result(); result.setAssessmentType(AssessmentType.AUTOMATIC); result.setSuccessful(buildResult.isBuildSuccessful()); - result.setCompletionDate(buildResult.getBuildRunDate()); + result.setCompletionDate(buildResult.buildRunDate()); // this only sets the score to a temporary value, the real score is calculated in the grading service - result.setScore(buildResult.getBuildScore(), exercise.getCourseViaExerciseGroupOrCourseMember()); + result.setScore(buildResult.buildScore(), exercise.getCourseViaExerciseGroupOrCourseMember()); result.setParticipation((Participation) participation); addFeedbackToResult(result, buildResult); @@ -63,8 +59,8 @@ public Result createResultFromBuildResult(AbstractBuildResultNotificationDTO bui * @param result the result for which the feedback should be added * @param buildResult The build result */ - private void addFeedbackToResult(Result result, AbstractBuildResultNotificationDTO buildResult) { - final var jobs = buildResult.getBuildJobs(); + private void addFeedbackToResult(Result result, BuildResultNotification buildResult) { + final var jobs = buildResult.jobs(); final var programmingExercise = (ProgrammingExercise) result.getParticipation().getExercise(); // 1) add feedback for failed and passed test cases @@ -72,32 +68,28 @@ private void addFeedbackToResult(Result result, AbstractBuildResultNotificationD // 2) process static code analysis feedback addStaticCodeAnalysisFeedbackToResult(result, buildResult, programmingExercise); - - // 3) process testwise coverage analysis report - addTestwiseCoverageReportToResult(result, buildResult, programmingExercise); } - private void addTestCaseFeedbacksToResult(Result result, List jobs, ProgrammingExercise programmingExercise) { + private void addTestCaseFeedbacksToResult(Result result, List jobs, ProgrammingExercise programmingExercise) { var activeTestCases = testCaseRepository.findByExerciseIdAndActive(programmingExercise.getId(), true); - for (final var job : jobs) { - for (final var failedTest : job.getFailedTests()) { - result.addFeedback( - feedbackCreationService.createFeedbackFromTestCase(failedTest.getName(), failedTest.getTestMessages(), false, programmingExercise, activeTestCases)); - } - result.setTestCaseCount(result.getTestCaseCount() + job.getFailedTests().size()); + jobs.forEach(job -> { + job.failedTests().forEach(failedTest -> { + result.addFeedback(feedbackCreationService.createFeedbackFromTestCase(failedTest.name(), failedTest.testMessages(), false, programmingExercise, activeTestCases)); + }); + result.setTestCaseCount(result.getTestCaseCount() + job.failedTests().size()); - for (final var successfulTest : job.getSuccessfulTests()) { + for (final var successfulTest : job.successfulTests()) { result.addFeedback( - feedbackCreationService.createFeedbackFromTestCase(successfulTest.getName(), successfulTest.getTestMessages(), true, programmingExercise, activeTestCases)); + feedbackCreationService.createFeedbackFromTestCase(successfulTest.name(), successfulTest.testMessages(), true, programmingExercise, activeTestCases)); } - result.setTestCaseCount(result.getTestCaseCount() + job.getSuccessfulTests().size()); - result.setPassedTestCaseCount(result.getPassedTestCaseCount() + job.getSuccessfulTests().size()); - } + result.setTestCaseCount(result.getTestCaseCount() + job.successfulTests().size()); + result.setPassedTestCaseCount(result.getPassedTestCaseCount() + job.successfulTests().size()); + }); } - private void addStaticCodeAnalysisFeedbackToResult(Result result, AbstractBuildResultNotificationDTO buildResult, ProgrammingExercise programmingExercise) { - final var staticCodeAnalysisReports = buildResult.getStaticCodeAnalysisReports(); + private void addStaticCodeAnalysisFeedbackToResult(Result result, BuildResultNotification buildResult, ProgrammingExercise programmingExercise) { + final var staticCodeAnalysisReports = buildResult.staticCodeAnalysisReports(); if (Boolean.TRUE.equals(programmingExercise.isStaticCodeAnalysisEnabled()) && staticCodeAnalysisReports != null && !staticCodeAnalysisReports.isEmpty()) { List scaFeedbackList = feedbackCreationService.createFeedbackFromStaticCodeAnalysisReports(staticCodeAnalysisReports); result.addFeedbacks(scaFeedbackList); @@ -105,18 +97,6 @@ private void addStaticCodeAnalysisFeedbackToResult(Result result, AbstractBuildR } } - private void addTestwiseCoverageReportToResult(Result result, AbstractBuildResultNotificationDTO buildResult, ProgrammingExercise programmingExercise) { - programmingExercise.setBuildConfig(programmingExerciseBuildConfigRepository.getProgrammingExerciseBuildConfigElseThrow(programmingExercise)); - if (Boolean.TRUE.equals(programmingExercise.getBuildConfig().isTestwiseCoverageEnabled())) { - var report = buildResult.getTestwiseCoverageReports(); - if (report != null) { - // since the test cases are not saved to the database yet, the test case is null for the entries - var coverageFileReportsWithoutTestsByTestCaseName = testwiseCoverageService.createTestwiseCoverageFileReportsWithoutTestsByTestCaseName(report); - result.setCoverageFileReportsByTestCaseName(coverageFileReportsWithoutTestsByTestCaseName); - } - } - } - /** * Find the ZonedDateTime of the first BuildLogEntry that contains the searchString in the log message. * diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/ci/ContinuousIntegrationResultService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/ci/ContinuousIntegrationResultService.java index b81239c44f0e..a63fc4871d4c 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/ci/ContinuousIntegrationResultService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/ci/ContinuousIntegrationResultService.java @@ -8,7 +8,7 @@ import de.tum.cit.aet.artemis.programming.domain.ProgrammingSubmission; import de.tum.cit.aet.artemis.programming.domain.ProjectType; import de.tum.cit.aet.artemis.programming.domain.build.BuildLogEntry; -import de.tum.cit.aet.artemis.programming.dto.AbstractBuildResultNotificationDTO; +import de.tum.cit.aet.artemis.programming.dto.BuildResultNotification; /** * Abstract service for managing entities related to continuous integration. @@ -21,7 +21,7 @@ public interface ContinuousIntegrationResultService { * @param requestBody the object sent from the CI system to Artemis * @return the DTO with all information in Java Object form */ - AbstractBuildResultNotificationDTO convertBuildResult(Object requestBody); + BuildResultNotification convertBuildResult(Object requestBody); /** * Generate an Artemis result object from the CI build result. Will use the test case results and issues in static code analysis as result feedback. @@ -30,7 +30,7 @@ public interface ContinuousIntegrationResultService { * @param participation to attach result to. * @return the created Artemis result with a score, completion date, etc. */ - Result createResultFromBuildResult(AbstractBuildResultNotificationDTO buildResult, ProgrammingExerciseParticipation participation); + Result createResultFromBuildResult(BuildResultNotification buildResult, ProgrammingExerciseParticipation participation); /** * Extract the build log statistics from the BuildLogEntries and persist a BuildLogStatisticsEntry. diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/ci/notification/dto/TestCaseDTO.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/ci/notification/dto/TestCaseDTO.java index e4e15c9227bf..5ae570f5b2d5 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/ci/notification/dto/TestCaseDTO.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/ci/notification/dto/TestCaseDTO.java @@ -14,13 +14,13 @@ import com.fasterxml.jackson.annotation.Nulls; import de.tum.cit.aet.artemis.assessment.domain.Feedback; -import de.tum.cit.aet.artemis.programming.dto.TestCaseBaseDTO; +import de.tum.cit.aet.artemis.programming.dto.TestCaseBase; @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_EMPTY) public record TestCaseDTO(String name, String classname, double time, @JsonProperty("failures") @JsonSetter(nulls = Nulls.AS_EMPTY) List failures, @JsonProperty("errors") @JsonSetter(nulls = Nulls.AS_EMPTY) List errors, - @JsonProperty("successInfos") @JsonSetter(nulls = Nulls.AS_EMPTY) List successInfos) implements TestCaseBaseDTO { + @JsonProperty("successInfos") @JsonSetter(nulls = Nulls.AS_EMPTY) List successInfos) implements TestCaseBase { @JsonIgnore public boolean isSuccessful() { @@ -28,12 +28,7 @@ public boolean isSuccessful() { } @Override - public String getName() { - return name; - } - - @Override - public List getTestMessages() { + public List testMessages() { return extractMessage().map(Collections::singletonList).orElse(Collections.emptyList()); } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/ci/notification/dto/TestResultsDTO.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/ci/notification/dto/TestResultsDTO.java index cd029f5adf5e..9bb2e13c588a 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/ci/notification/dto/TestResultsDTO.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/ci/notification/dto/TestResultsDTO.java @@ -4,7 +4,6 @@ import java.util.List; import java.util.stream.Stream; -import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; @@ -15,156 +14,64 @@ import de.tum.cit.aet.artemis.programming.domain.RepositoryType; import de.tum.cit.aet.artemis.programming.domain.build.BuildLogEntry; -import de.tum.cit.aet.artemis.programming.dto.AbstractBuildResultNotificationDTO; -import de.tum.cit.aet.artemis.programming.dto.BuildJobDTOInterface; +import de.tum.cit.aet.artemis.programming.dto.BuildJobInterface; +import de.tum.cit.aet.artemis.programming.dto.BuildResultNotification; import de.tum.cit.aet.artemis.programming.dto.StaticCodeAnalysisReportDTO; import de.tum.cit.aet.artemis.programming.service.ci.notification.BuildLogParseUtils; @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_EMPTY) // Note: due to limitations with inheritance, we cannot declare this as record, but we can use it in a similar way with final fields -public class TestResultsDTO extends AbstractBuildResultNotificationDTO { - - private final int successful; - - private final int skipped; - - private final int errors; - - private final int failures; - - private final String fullName; - - private final List commits; - - private final List results; - - private final List staticCodeAnalysisReports; - - private final List testwiseCoverageReport; - - private final ZonedDateTime runDate; - - private final boolean isBuildSuccessful; - - private final List logs; - - @JsonCreator - public TestResultsDTO(@JsonProperty("successful") int successful, @JsonProperty("skipped") int skipped, @JsonProperty("errors") int errors, - @JsonProperty("failures") int failures, @JsonProperty("fullName") String fullName, @JsonProperty("commits") @JsonSetter(nulls = Nulls.AS_EMPTY) List commits, - @JsonProperty("results") @JsonSetter(nulls = Nulls.AS_EMPTY) List results, - @JsonProperty("staticCodeAnalysisReports") @JsonSetter(nulls = Nulls.AS_EMPTY) List staticCodeAnalysisReports, - @JsonProperty("testwiseCoverageReport") @JsonSetter(nulls = Nulls.AS_EMPTY) List testwiseCoverageReport, - @JsonProperty("runDate") ZonedDateTime runDate, @JsonProperty("isBuildSuccessful") boolean isBuildSuccessful, - @JsonProperty("logs") @JsonSetter(nulls = Nulls.AS_EMPTY) List logs) { - this.successful = successful; - this.skipped = skipped; - this.errors = errors; - this.failures = failures; - this.fullName = fullName; - this.commits = commits; - this.results = results; - this.staticCodeAnalysisReports = staticCodeAnalysisReports; - this.testwiseCoverageReport = testwiseCoverageReport; - this.runDate = runDate; - this.isBuildSuccessful = isBuildSuccessful; - this.logs = logs; - } +public record TestResultsDTO(@JsonProperty("successful") int successful, @JsonProperty("skipped") int skipped, @JsonProperty("errors") int errors, + @JsonProperty("failures") int failures, @JsonProperty("fullName") String fullName, @JsonProperty("commits") @JsonSetter(nulls = Nulls.AS_EMPTY) List commits, + @JsonProperty("results") @JsonSetter(nulls = Nulls.AS_EMPTY) List results, + @JsonProperty("staticCodeAnalysisReports") @JsonSetter(nulls = Nulls.AS_EMPTY) List staticCodeAnalysisReports, + @JsonProperty("runDate") ZonedDateTime runDate, @JsonProperty("isBuildSuccessful") boolean isBuildSuccessful, + @JsonProperty("logs") @JsonSetter(nulls = Nulls.AS_EMPTY) List logs) implements BuildResultNotification { public static TestResultsDTO convert(Object someResult) { return new ObjectMapper().registerModule(new JavaTimeModule()).convertValue(someResult, TestResultsDTO.class); } - public int getSuccessful() { - return successful; - } - - public int getSkipped() { - return skipped; - } - - public int getErrors() { - return errors; - } - - public int getFailures() { - return failures; - } - - public String getFullName() { - return fullName; - } - - public ZonedDateTime getRunDate() { - return runDate; - } - - public List getLogs() { - return this.logs; - } - @Override - public ZonedDateTime getBuildRunDate() { - return getRunDate(); + public ZonedDateTime buildRunDate() { + return runDate(); } @Override - protected String getCommitHashFromAssignmentRepo() { + public String assignmentRepoCommitHash() { final var testRepoNameSuffix = RepositoryType.TESTS.getName(); - final var firstCommit = getCommits().stream().filter(commit -> !commit.repositorySlug().endsWith(testRepoNameSuffix)).findFirst(); + final var firstCommit = commits().stream().filter(commit -> !commit.repositorySlug().endsWith(testRepoNameSuffix)).findFirst(); return firstCommit.map(CommitDTO::hash).orElse(null); } @Override - protected String getCommitHashFromTestsRepo() { + public String testsRepoCommitHash() { final var testRepoNameSuffix = RepositoryType.TESTS.getName(); - final var firstCommit = getCommits().stream().filter(commit -> commit.repositorySlug().endsWith(testRepoNameSuffix)).findFirst(); + final var firstCommit = commits().stream().filter(commit -> commit.repositorySlug().endsWith(testRepoNameSuffix)).findFirst(); return firstCommit.map(CommitDTO::hash).orElse(null); } @Override - public String getBranchNameFromAssignmentRepo() { + public String assignmentRepoBranchName() { final var testRepoNameSuffix = RepositoryType.TESTS.getName(); - final var firstCommit = getCommits().stream().filter(commit -> !commit.repositorySlug().endsWith(testRepoNameSuffix)).findFirst(); + final var firstCommit = commits().stream().filter(commit -> !commit.repositorySlug().endsWith(testRepoNameSuffix)).findFirst(); return firstCommit.map(CommitDTO::branchName).orElse(null); } private int getSum() { - return getSkipped() + getFailures() + getErrors() + getSuccessful(); - } - - @Override - public boolean isBuildSuccessful() { - return isBuildSuccessful; + return skipped() + failures() + errors() + successful(); } @Override - public Double getBuildScore() { + public Double buildScore() { final var testSum = getSum(); - return testSum == 0 ? 0D : ((double) getSuccessful() / testSum) * 100D; - } - - public List getCommits() { - return commits; - } - - public List getResults() { - return results; - } - - @Override - public List getStaticCodeAnalysisReports() { - return staticCodeAnalysisReports; - } - - @Override - public List getTestwiseCoverageReports() { - return testwiseCoverageReport; + return testSum == 0 ? 0D : ((double) successful() / testSum) * 100D; } @Override public boolean hasArtifact() { - // TODO: this is not available in Jenkins or GitLab CI yet + // NOTE: this is not available in Jenkins return false; } @@ -175,13 +82,13 @@ public boolean hasLogs() { @Override public List extractBuildLogs() { - var buildLogs = BuildLogParseUtils.parseBuildLogsFromLogs(getLogs()); + var buildLogs = BuildLogParseUtils.parseBuildLogsFromLogs(logs()); return filterBuildLogs(buildLogs); } @Override - public List getBuildJobs() { - return getResults(); + public List jobs() { + return results(); } /** diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/ci/notification/dto/TestSuiteDTO.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/ci/notification/dto/TestSuiteDTO.java index 9be20c1a8ad9..ac3702222c00 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/ci/notification/dto/TestSuiteDTO.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/ci/notification/dto/TestSuiteDTO.java @@ -9,23 +9,23 @@ import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.Nulls; -import de.tum.cit.aet.artemis.programming.dto.BuildJobDTOInterface; -import de.tum.cit.aet.artemis.programming.dto.TestCaseBaseDTO; +import de.tum.cit.aet.artemis.programming.dto.BuildJobInterface; +import de.tum.cit.aet.artemis.programming.dto.TestCaseBase; @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_EMPTY) public record TestSuiteDTO(String name, double time, int errors, int skipped, int failures, int tests, - @JsonProperty("testCases") @JsonSetter(nulls = Nulls.AS_EMPTY) List testCases) implements BuildJobDTOInterface { + @JsonProperty("testCases") @JsonSetter(nulls = Nulls.AS_EMPTY) List testCases) implements BuildJobInterface { @Override @JsonIgnore - public List getFailedTests() { + public List failedTests() { return testCases.stream().filter(testCase -> !testCase.isSuccessful()).toList(); } @Override @JsonIgnore - public List getSuccessfulTests() { + public List successfulTests() { return testCases.stream().filter(TestCaseDTO::isSuccessful).toList(); } } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/ci/notification/dto/TestwiseCoverageReportDTO.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/ci/notification/dto/TestwiseCoverageReportDTO.java deleted file mode 100644 index ef94ea89542f..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/ci/notification/dto/TestwiseCoverageReportDTO.java +++ /dev/null @@ -1,115 +0,0 @@ -package de.tum.cit.aet.artemis.programming.service.ci.notification.dto; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; - -// TODO: convert to record -@JsonIgnoreProperties(ignoreUnknown = true) -@JsonInclude(JsonInclude.Include.NON_EMPTY) -public class TestwiseCoverageReportDTO implements Serializable { - - @JsonProperty("uniformPath") - private String uniformPath; - - @JsonProperty("duration") - private double duration; - - @JsonProperty("content") - private String content; - - @JsonProperty("paths") - private List coveredPathsPerTestDTOs = new ArrayList<>(); - - public String getUniformPath() { - return uniformPath; - } - - public void setUniformPath(String uniformPath) { - this.uniformPath = uniformPath; - } - - public double getDuration() { - return duration; - } - - public void setDuration(double duration) { - this.duration = duration; - } - - public String getContent() { - return content; - } - - public void setContent(String content) { - this.content = content; - } - - public List getCoveredPathsPerTestDTOs() { - return coveredPathsPerTestDTOs; - } - - public void setCoveredPathsPerTestDTOs(List coveredPathsPerTestDTOs) { - this.coveredPathsPerTestDTOs = coveredPathsPerTestDTOs; - } - - // TODO: convert to record - @JsonIgnoreProperties(ignoreUnknown = true) - @JsonInclude(JsonInclude.Include.NON_EMPTY) - public static final class CoveredPathsPerTestDTO implements Serializable { - - @JsonProperty("path") - private String path; - - @JsonProperty("files") - private List coveredFilesPerTestDTOs = new ArrayList<>(); - - public String getPath() { - return path; - } - - public void setPath(String path) { - this.path = path; - } - - public List getCoveredFilesPerTestDTOs() { - return coveredFilesPerTestDTOs; - } - - public void setCoveredFilesPerTestDTOs(List coveredFilesPerTestDTOs) { - this.coveredFilesPerTestDTOs = coveredFilesPerTestDTOs; - } - } - - // TODO: convert to record - @JsonIgnoreProperties(ignoreUnknown = true) - @JsonInclude(JsonInclude.Include.NON_EMPTY) - public static final class CoveredFilesPerTestDTO implements Serializable { - - @JsonProperty("fileName") - private String fileName; - - @JsonProperty("coveredLines") - private String coveredLinesWithRanges; - - public String getFileName() { - return fileName; - } - - public void setFileName(String fileName) { - this.fileName = fileName; - } - - public String getCoveredLinesWithRanges() { - return coveredLinesWithRanges; - } - - public void setCoveredLinesWithRanges(String coveredLinesWithRanges) { - this.coveredLinesWithRanges = coveredLinesWithRanges; - } - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/gitlabci/GitLabCIProgrammingLanguageFeatureService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/gitlabci/GitLabCIProgrammingLanguageFeatureService.java index 0c71114e13bb..357cf05d97f8 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/gitlabci/GitLabCIProgrammingLanguageFeatureService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/gitlabci/GitLabCIProgrammingLanguageFeatureService.java @@ -23,9 +23,9 @@ public class GitLabCIProgrammingLanguageFeatureService extends ProgrammingLanguageFeatureService { public GitLabCIProgrammingLanguageFeatureService() { - programmingLanguageFeatures.put(EMPTY, new ProgrammingLanguageFeature(EMPTY, false, false, false, false, false, List.of(), false, false)); - programmingLanguageFeatures.put(JAVA, new ProgrammingLanguageFeature(JAVA, false, false, false, true, false, List.of(PLAIN_MAVEN, MAVEN_MAVEN), false, false)); - programmingLanguageFeatures.put(JAVASCRIPT, new ProgrammingLanguageFeature(JAVASCRIPT, false, false, true, false, false, List.of(), false, false)); - programmingLanguageFeatures.put(RUST, new ProgrammingLanguageFeature(RUST, false, false, true, false, false, List.of(), false, false)); + programmingLanguageFeatures.put(EMPTY, new ProgrammingLanguageFeature(EMPTY, false, false, false, false, false, List.of(), false)); + programmingLanguageFeatures.put(JAVA, new ProgrammingLanguageFeature(JAVA, false, false, false, true, false, List.of(PLAIN_MAVEN, MAVEN_MAVEN), false)); + programmingLanguageFeatures.put(JAVASCRIPT, new ProgrammingLanguageFeature(JAVASCRIPT, false, false, true, false, false, List.of(), false)); + programmingLanguageFeatures.put(RUST, new ProgrammingLanguageFeature(RUST, false, false, true, false, false, List.of(), false)); } } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/gitlabci/GitLabCIResultService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/gitlabci/GitLabCIResultService.java index fa63482e88ca..0c6475c1e236 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/gitlabci/GitLabCIResultService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/gitlabci/GitLabCIResultService.java @@ -8,22 +8,18 @@ import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; -import de.tum.cit.aet.artemis.assessment.repository.FeedbackRepository; import de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage; import de.tum.cit.aet.artemis.programming.domain.ProgrammingSubmission; import de.tum.cit.aet.artemis.programming.domain.ProjectType; import de.tum.cit.aet.artemis.programming.domain.build.BuildLogEntry; import de.tum.cit.aet.artemis.programming.domain.build.BuildLogStatisticsEntry; -import de.tum.cit.aet.artemis.programming.dto.AbstractBuildResultNotificationDTO; +import de.tum.cit.aet.artemis.programming.dto.BuildResultNotification; import de.tum.cit.aet.artemis.programming.repository.BuildLogStatisticsEntryRepository; import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseBuildConfigRepository; import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseTestCaseRepository; -import de.tum.cit.aet.artemis.programming.repository.ProgrammingSubmissionRepository; -import de.tum.cit.aet.artemis.programming.service.BuildLogEntryService; import de.tum.cit.aet.artemis.programming.service.ProgrammingExerciseFeedbackCreationService; import de.tum.cit.aet.artemis.programming.service.ci.AbstractContinuousIntegrationResultService; import de.tum.cit.aet.artemis.programming.service.ci.notification.dto.TestResultsDTO; -import de.tum.cit.aet.artemis.programming.service.hestia.TestwiseCoverageService; // Gitlab support will be removed in 8.0.0. Please migrate to LocalVC using e.g. the PR https://github.com/ls1intum/Artemis/pull/8972 @Deprecated(since = "7.5.0", forRemoval = true) @@ -34,15 +30,13 @@ public class GitLabCIResultService extends AbstractContinuousIntegrationResultSe private static final Logger log = LoggerFactory.getLogger(GitLabCIResultService.class); - public GitLabCIResultService(ProgrammingSubmissionRepository programmingSubmissionRepository, FeedbackRepository feedbackRepository, BuildLogEntryService buildLogService, - BuildLogStatisticsEntryRepository buildLogStatisticsEntryRepository, TestwiseCoverageService testwiseCoverageService, - ProgrammingExerciseFeedbackCreationService feedbackCreationService, ProgrammingExerciseTestCaseRepository testCaseRepository, - ProgrammingExerciseBuildConfigRepository programmingExerciseBuildConfigRepository) { - super(testCaseRepository, buildLogStatisticsEntryRepository, testwiseCoverageService, feedbackCreationService, programmingExerciseBuildConfigRepository); + public GitLabCIResultService(BuildLogStatisticsEntryRepository buildLogStatisticsEntryRepository, ProgrammingExerciseFeedbackCreationService feedbackCreationService, + ProgrammingExerciseTestCaseRepository testCaseRepository, ProgrammingExerciseBuildConfigRepository programmingExerciseBuildConfigRepository) { + super(testCaseRepository, buildLogStatisticsEntryRepository, feedbackCreationService, programmingExerciseBuildConfigRepository); } @Override - public AbstractBuildResultNotificationDTO convertBuildResult(Object requestBody) { + public BuildResultNotification convertBuildResult(Object requestBody) { return TestResultsDTO.convert(requestBody); } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/gitlabci/GitLabCIService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/gitlabci/GitLabCIService.java index e0ec98ed52cf..7226e40b16db 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/gitlabci/GitLabCIService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/gitlabci/GitLabCIService.java @@ -314,7 +314,7 @@ public void deleteBuildPlan(String projectKey, String buildPlanId) { @Override public String getPlanKey(Object requestBody) throws ContinuousIntegrationException { TestResultsDTO dto = TestResultsDTO.convert(requestBody); - return dto.getFullName(); + return dto.fullName(); } @Override diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/CodeHintService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/CodeHintService.java deleted file mode 100644 index f04de8a56db9..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/CodeHintService.java +++ /dev/null @@ -1,187 +0,0 @@ -package de.tum.cit.aet.artemis.programming.service.hestia; - -import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; - -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.annotation.Profile; -import org.springframework.stereotype.Service; - -import de.tum.cit.aet.artemis.core.exception.BadRequestAlertException; -import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise; -import de.tum.cit.aet.artemis.programming.domain.hestia.CodeHint; -import de.tum.cit.aet.artemis.programming.domain.hestia.ProgrammingExerciseSolutionEntry; -import de.tum.cit.aet.artemis.programming.domain.hestia.ProgrammingExerciseTask; -import de.tum.cit.aet.artemis.programming.repository.hestia.CodeHintRepository; -import de.tum.cit.aet.artemis.programming.repository.hestia.ProgrammingExerciseSolutionEntryRepository; -import de.tum.cit.aet.artemis.programming.repository.hestia.ProgrammingExerciseTaskRepository; - -@Profile(PROFILE_CORE) -@Service -public class CodeHintService { - - private static final Logger log = LoggerFactory.getLogger(CodeHintService.class); - - private final CodeHintRepository codeHintRepository; - - private final ProgrammingExerciseTaskRepository taskRepository; - - private final ProgrammingExerciseSolutionEntryRepository solutionEntryRepository; - - public CodeHintService(CodeHintRepository codeHintRepository, ProgrammingExerciseTaskRepository taskRepository, - ProgrammingExerciseSolutionEntryRepository solutionEntryRepository) { - this.codeHintRepository = codeHintRepository; - this.taskRepository = taskRepository; - this.solutionEntryRepository = solutionEntryRepository; - } - - /** - * Generate {@link CodeHint}s for all {@link ProgrammingExerciseTask}s of an exercise. - * If requested old code hints will be deleted otherwise the new ones will be added to the existing ones. (This might however break the old ones) - * If a task does not have any test cases with solution entries it will not get a code hint. - * - * @param exercise The programming exercise - * @param deleteOldCodeHints Whether old code hint should be deleted - * @return The list of all newly generated code hints - */ - public List generateCodeHintsForExercise(ProgrammingExercise exercise, boolean deleteOldCodeHints) { - log.info("Generating code hints for exercise {} with deleteOldCodeHints={}", exercise.getId(), deleteOldCodeHints); - - var tasks = taskRepository.findByExerciseIdWithTestCaseAndSolutionEntriesElseThrow(exercise.getId()); - - return tasks.stream().map(task -> generateCodeHintForTask(task, deleteOldCodeHints)).filter(Optional::isPresent).map(Optional::get).toList(); - } - - /** - * Generate a single {@link CodeHint} for a single {@link ProgrammingExerciseTask} - * If requested old code hints will be deleted otherwise the new one will be added to the existing ones. (This might however break the old ones) - * If the task does not have any test cases with solution entries it will not get a code hint. - * - * @param task The programming exercise task - * @param deleteOldCodeHints Whether old code hint should be deleted - * @return The newly created code hint if one was needed - */ - public Optional generateCodeHintForTask(ProgrammingExerciseTask task, boolean deleteOldCodeHints) { - log.info("Generating code hints for task {} ({}) in exercise {} with deleteOldCodeHints={}", task.getId(), task.getTaskName(), task.getExercise().getId(), - deleteOldCodeHints); - - var codeHint = new CodeHint(); - codeHint.setExercise(task.getExercise()); - codeHint.setProgrammingExerciseTask(task); - codeHint.setTitle("Code hint for task " + task.getTaskName()); - - var solutionEntries = task.getTestCases().stream().flatMap(testCase -> testCase.getSolutionEntries().stream()).peek(solutionEntry -> solutionEntry.setCodeHint(codeHint)) - .collect(Collectors.toSet()); - - if (deleteOldCodeHints) { - deleteCodeHintsForTask(task); - } - if (solutionEntries.isEmpty()) { - return Optional.empty(); - } - - codeHint.setSolutionEntries(solutionEntries); - codeHintRepository.save(codeHint); - solutionEntryRepository.saveAll(solutionEntries); - - return Optional.of(codeHint); - } - - /** - * Deletes all code hints of a {@link ProgrammingExerciseTask} - * - * @param task The programming exercise task - */ - public void deleteCodeHintsForTask(ProgrammingExerciseTask task) { - log.info("Deleting all code hints of task {} ({}) in exercise {}", task.getId(), task.getTaskName(), task.getExercise().getId()); - - var codeHints = codeHintRepository.findByTaskIdWithSolutionEntries(task.getId()); - var solutionEntries = codeHints.stream().flatMap(codeHint -> codeHint.getSolutionEntries().stream()).peek(solutionEntry -> solutionEntry.setCodeHint(null)).toList(); - solutionEntryRepository.saveAll(solutionEntries); - codeHintRepository.deleteAll(codeHints); - } - - /** - * Deletes a single code hint. - * Sets the code hint of all related solution entries to null before deleting. - * - * @param codeHint The code hint to be deleted - */ - public void deleteCodeHint(CodeHint codeHint) { - log.info("Deleting code hint {}", codeHint.getId()); - - var solutionEntries = solutionEntryRepository.findByCodeHintId(codeHint.getId()); - for (ProgrammingExerciseSolutionEntry solutionEntry : solutionEntries) { - solutionEntry.setCodeHint(null); - } - solutionEntryRepository.saveAll(solutionEntries); - codeHintRepository.delete(codeHint); - } - - /** - * Persists the updated solution entries for a code hint. The solution entries are loaded from the database and can result in three scenarios: - * 1. The code or test case for an existing entry is updated. - * 2. A new solution entry is created. The test case of the entry must be contained in hint related task. - * 3. An entry is removed from the code hint. This method does not delete the entry itself, but removes the reference to the corresponding hint. - * - * @param hint the code hint containing the solution entries to be updated - */ - public void updateSolutionEntriesForCodeHint(CodeHint hint) { - var optionalTask = taskRepository.findByCodeHintIdWithTestCases(hint.getId()); - - if (optionalTask.isEmpty()) { - throw new BadRequestAlertException("No task has been assigned to the code hint.", "CodeHint", "codeHint"); - } - - var task = optionalTask.get(); - - var savedSolutionEntries = solutionEntryRepository.findByCodeHintId(hint.getId()); - - var newEntries = hint.getSolutionEntries().stream().filter(entry -> savedSolutionEntries.stream().noneMatch(savedEntry -> savedEntry.getId().equals(entry.getId()))) - .peek(entry -> entry.setCodeHint(hint)).toList(); - - // check that the task of the hint contains all test cases of every solution entry to be saved - // we only assume changes on the test case of an entry, if the value is defined - boolean hasUnrelatedTestCaseEntries = newEntries.stream() - .anyMatch(entry -> task.getTestCases().stream().noneMatch(containedTestCase -> containedTestCase.getId().equals(entry.getTestCase().getId()))); - if (hasUnrelatedTestCaseEntries) { - throw new BadRequestAlertException("There is at least one solution entry that references a test case that does not belong to the task of the code hint.", "Code Hint", - "codeHint"); - } - - var result = new HashSet<>(newEntries); - - var updatedEntries = new HashSet<>(hint.getSolutionEntries()); - newEntries.forEach(updatedEntries::remove); - - var removedEntries = savedSolutionEntries.stream() - .filter(savedEntry -> hint.getSolutionEntries().stream().noneMatch(updatedEntry -> savedEntry.getId().equals(updatedEntry.getId()))) - .peek(entryToRemove -> entryToRemove.setCodeHint(null)).toList(); - - updatedEntries.forEach(updatedEntry -> { - var optionalMatch = savedSolutionEntries.stream().filter(savedEntry -> savedEntry.getId().equals(updatedEntry.getId())).findFirst(); - if (optionalMatch.isPresent()) { - var match = optionalMatch.get(); - match.setLine(updatedEntry.getLine()); - match.setPreviousLine(updatedEntry.getPreviousLine()); - match.setFilePath(updatedEntry.getFilePath()); - match.setCode(updatedEntry.getCode()); - match.setPreviousCode(updatedEntry.getPreviousCode()); - - // update test case if defined - match.setTestCase(updatedEntry.getTestCase() != null ? updatedEntry.getTestCase() : match.getTestCase()); - result.add(match); - } - }); - hint.setSolutionEntries(result); - solutionEntryRepository.saveAll(result); - solutionEntryRepository.saveAll(removedEntries); - - codeHintRepository.save(hint); - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/ExerciseHintService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/ExerciseHintService.java deleted file mode 100644 index 477bcaf88749..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/ExerciseHintService.java +++ /dev/null @@ -1,326 +0,0 @@ -package de.tum.cit.aet.artemis.programming.service.hestia; - -import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; - -import java.time.ZonedDateTime; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; - -import org.springframework.cache.annotation.Cacheable; -import org.springframework.context.annotation.Profile; -import org.springframework.stereotype.Service; - -import de.tum.cit.aet.artemis.assessment.domain.Feedback; -import de.tum.cit.aet.artemis.core.domain.User; -import de.tum.cit.aet.artemis.core.exception.BadRequestAlertException; -import de.tum.cit.aet.artemis.core.exception.ConflictException; -import de.tum.cit.aet.artemis.core.security.Role; -import de.tum.cit.aet.artemis.core.service.AuthorizationCheckService; -import de.tum.cit.aet.artemis.exercise.domain.Submission; -import de.tum.cit.aet.artemis.exercise.repository.StudentParticipationRepository; -import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise; -import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseTestCase; -import de.tum.cit.aet.artemis.programming.domain.hestia.ExerciseHint; -import de.tum.cit.aet.artemis.programming.domain.hestia.ExerciseHintActivation; -import de.tum.cit.aet.artemis.programming.domain.hestia.ProgrammingExerciseTask; -import de.tum.cit.aet.artemis.programming.repository.hestia.ExerciseHintActivationRepository; -import de.tum.cit.aet.artemis.programming.repository.hestia.ExerciseHintRepository; - -@Profile(PROFILE_CORE) -@Service -public class ExerciseHintService { - - private final AuthorizationCheckService authCheckService; - - private final ExerciseHintRepository exerciseHintRepository; - - private final ProgrammingExerciseTaskService programmingExerciseTaskService; - - private final StudentParticipationRepository studentParticipationRepository; - - private final ExerciseHintActivationRepository exerciseHintActivationRepository; - - public ExerciseHintService(AuthorizationCheckService authCheckService, ExerciseHintRepository exerciseHintRepository, - ProgrammingExerciseTaskService programmingExerciseTaskService, StudentParticipationRepository studentParticipationRepository, - ExerciseHintActivationRepository exerciseHintActivationRepository) { - this.authCheckService = authCheckService; - this.exerciseHintRepository = exerciseHintRepository; - this.programmingExerciseTaskService = programmingExerciseTaskService; - this.studentParticipationRepository = studentParticipationRepository; - this.exerciseHintActivationRepository = exerciseHintActivationRepository; - } - - /** - * Copies the hints of an exercise to a new target exercise by cloning the hint objects and saving them - * resulting in new IDs for the copied hints. The contents stay the same. On top of that, all hints in the - * problem statement of the target exercise get replaced by the new IDs. - * - * @param template The template exercise containing the hints that should be copied - * @param target The new target exercise, to which all hints should get copied to. - * @return A map with the old hint id as a key and the new hint id as a value - */ - public Map copyExerciseHints(final ProgrammingExercise template, final ProgrammingExercise target) { - final Map hintIdMapping = new HashMap<>(); - target.setExerciseHints(template.getExerciseHints().stream().map(hint -> { - ExerciseHint copiedHint = hint.createCopy(); - copiedHint.setExercise(target); - exerciseHintRepository.save(copiedHint); - hintIdMapping.put(hint.getId(), copiedHint.getId()); - return copiedHint; - }).collect(Collectors.toSet())); - - return hintIdMapping; - } - - /** - * Sets the rating of an exercise hint for a user - * The rating is saved in the associated ExerciseHintActivation. - * - * @param exerciseHint The exercise hint to rate - * @param user The user that submits the rating - * @param ratingValue The value of the rating - */ - public void rateExerciseHint(ExerciseHint exerciseHint, User user, Integer ratingValue) { - if (ratingValue < 1 || ratingValue > 5) { - throw new BadRequestAlertException("rating has to be between 1 and 5", "exerciseHint", "ratingValue.invalid", false); - } - var exerciseHintActivation = exerciseHintActivationRepository.findByExerciseHintAndUserElseThrow(exerciseHint.getId(), user.getId()); - exerciseHintActivation.setRating(ratingValue); - exerciseHintActivationRepository.save(exerciseHintActivation); - } - - /** - * Activates an ExerciseHint for a user. - * After activation the user can view the full content of the hint without restrictions. - * This action cannot be undone - * - * @param exerciseHint The exercise hint - * @param user The user - * @return true if the hint was activated - */ - public boolean activateHint(ExerciseHint exerciseHint, User user) { - // Check if the user has access to the exercise - // This is done here to prevent illegal activation of hints in all possible future cases - authCheckService.checkHasAtLeastRoleForExerciseElseThrow(Role.STUDENT, exerciseHint.getExercise(), user); - - // Check if the user can activate the hint - if (!getAvailableExerciseHints(exerciseHint.getExercise(), user).contains(exerciseHint)) { - return false; - } - - // Check if the hint was already activated - if (exerciseHintActivationRepository.findByExerciseHintAndUser(exerciseHint.getId(), user.getId()).isPresent()) { - return false; - } - - var exerciseHintActivation = new ExerciseHintActivation(); - exerciseHintActivation.setExerciseHint(exerciseHint); - exerciseHintActivation.setUser(user); - exerciseHintActivation.setActivationDate(ZonedDateTime.now()); - exerciseHintActivationRepository.save(exerciseHintActivation); - return true; - } - - /** - * Retrieves all exercise hints that a user has activated for an exercise. - * - * @param exercise The programming exercise - * @param user The user - * @return All activated exercise hints for this user and exercise - */ - public Set getActivatedExerciseHints(ProgrammingExercise exercise, User user) { - return exerciseHintActivationRepository.findByExerciseAndUserWithExerciseHintRelations(exercise.getId(), user.getId()).stream().map(exerciseHintActivation -> { - var exerciseHint = exerciseHintActivation.getExerciseHint(); - exerciseHint.setCurrentUserRating(exerciseHintActivation.getRating()); - return exerciseHint; - }).collect(Collectors.toSet()); - } - - /** - * Returns all exercise hints that the user can currently activate for a given programming exercise. - * Exercise hints will be shown for the first task that meets the following conditions: - * (1) the subsequent number of the latest submissions the previous task is successful is greater or equal to the hint's threshold - * (2) the subsequent number of the latest submissions the current task is unsuccessful is greater or equal to the hint's threshold - * If no task matches these conditions, no exercise hints will be returned - * Note: A task is successful, if the feedback within the submission is positive for all associated test cases within this task - * - * @param exercise The programming exercise - * @param user The user - * @return All available exercise hints - */ - public Set getAvailableExerciseHints(ProgrammingExercise exercise, User user) { - var exerciseHints = exerciseHintRepository.findByExerciseId(exercise.getId()); - if (exerciseHints.isEmpty()) { - return new HashSet<>(); - } - - var submissions = getSubmissionsForStudent(exercise, user); - - if (submissions.isEmpty()) { - return new HashSet<>(); - } - - var latestResult = submissions.getFirst().getLatestResult(); - - // latest submissions has no result or latest result has no feedback (most commonly due to a build error) - if (latestResult == null || latestResult.getFeedbacks().isEmpty()) { - return new HashSet<>(); - } - var tasks = programmingExerciseTaskService.getSortedTasks(exercise); - - var subsequentNumberOfUnsuccessfulSubmissionsByTask = tasks.stream() - .collect(Collectors.toMap(task -> task, task -> subsequentNumberOfSubmissionsForTaskWithStatus(submissions, task, false))); - var subsequentNumberOfSuccessfulSubmissionsByTask = tasks.stream() - .collect(Collectors.toMap(task -> task, task -> subsequentNumberOfSubmissionsForTaskWithStatus(submissions, task, true))); - - var availableHints = new HashSet(); - - for (int i = 0; i < tasks.size(); i++) { - var task = tasks.get(i); - int subsequentNumberOfUnsuccessfulSubmissionsForCurrentTask = subsequentNumberOfUnsuccessfulSubmissionsByTask.get(task); - // current task is successful - if (subsequentNumberOfUnsuccessfulSubmissionsForCurrentTask == 0) { - continue; - } - - var hintsInTask = exerciseHints.stream() - .filter(hint -> hint.getProgrammingExerciseTask() != null && Objects.equals(hint.getProgrammingExerciseTask().getId(), task.getId())) - .collect(Collectors.toSet()); - // no hints exist for the current task - if (hintsInTask.isEmpty()) { - continue; - } - - Optional subsequentNumberSuccessfulSubmissionsForPreviousTask; - if (i == 0) { - subsequentNumberSuccessfulSubmissionsForPreviousTask = Optional.empty(); - } - else { - subsequentNumberSuccessfulSubmissionsForPreviousTask = Optional.of(subsequentNumberOfSuccessfulSubmissionsByTask.get(tasks.get(i - 1))); - } - - // skip current task if the previous task was not successful - if (0 == subsequentNumberSuccessfulSubmissionsForPreviousTask.orElse(-1)) { - continue; - } - - // add the available hints for the current task - var availableHintsForCurrentTask = getAvailableExerciseHintsForTask(subsequentNumberSuccessfulSubmissionsForPreviousTask, - subsequentNumberOfUnsuccessfulSubmissionsForCurrentTask, hintsInTask); - if (!availableHintsForCurrentTask.isEmpty()) { - availableHints.addAll(availableHintsForCurrentTask); - break; - } - } - // Hints with a threshold of 0 will always be displayed - availableHints.addAll(exerciseHints.stream().filter(hint -> hint.getDisplayThreshold() == 0).toList()); - return availableHints; - } - - private boolean isTaskSuccessfulInSubmission(ProgrammingExerciseTask task, Submission submission) { - var result = submission.getLatestResult(); - if (result == null || result.getFeedbacks().isEmpty()) { - return false; - } - var testCasesInTask = task.getTestCases(); - var feedbacks = result.getFeedbacks(); - // a task is successful if feedback for all in the task linked tests exist and if these tests all passed. - var feedbackForTask = testCasesInTask.stream().map(testCase -> findFeedbackForTestCase(testCase, feedbacks)).filter(Optional::isPresent).map(Optional::get).toList(); - if (feedbackForTask.size() != testCasesInTask.size()) { - // some expected test cases were not executed in the student's result - return false; - } - return feedbackForTask.stream().allMatch(feedback -> Boolean.TRUE.equals(feedback.isPositive())); - } - - /** - * Finds the first feedback in the provided list, that was created by the test case. - * - * @param testCase the test case to search the linked feedback for - * @return the feedback related by the test case, if present - */ - private Optional findFeedbackForTestCase(ProgrammingExerciseTestCase testCase, List feedbacks) { - return feedbacks.stream().filter(feedback -> testCase.equals(feedback.getTestCase())).findAny(); - } - - private List getSubmissionsForStudent(ProgrammingExercise exercise, User student) { - List submissions = new ArrayList<>(); - - var ratedStudentParticipation = studentParticipationRepository.findByExerciseIdAndStudentIdAndTestRunWithEagerSubmissionsResultsFeedbacksTestCases(exercise.getId(), - student.getId(), false); - ratedStudentParticipation.ifPresent(participation -> submissions.addAll(participation.getSubmissions())); - - var practiceStudentParticipation = studentParticipationRepository.findByExerciseIdAndStudentIdAndTestRunWithEagerSubmissionsResultsFeedbacksTestCases(exercise.getId(), - student.getId(), true); - practiceStudentParticipation.ifPresent(participation -> submissions.addAll(participation.getSubmissions())); - - submissions.sort(Comparator.reverseOrder()); - - return submissions; - } - - private int subsequentNumberOfSubmissionsForTaskWithStatus(List submissions, ProgrammingExerciseTask task, boolean successful) { - int subsequentNumberSuccessfulSubmissionsForTask = 0; - for (Submission submission : submissions) { - if (isTaskSuccessfulInSubmission(task, submission) != successful) { - break; - } - subsequentNumberSuccessfulSubmissionsForTask++; - } - return subsequentNumberSuccessfulSubmissionsForTask; - } - - /** - * Filter hints that meet the following conditions: - * 1. the previous task (if existing) is successful for at least the hint's threshold value - * 2. the current task is unsuccessful for at least the hint's threshold value - * - * @param subsequentNumberSuccessfulSubmissionsForPreviousTask the subsequent number of the latest submissions the previous task is successful - * @param subsequentNumberOfUnsuccessfulSubmissionsForCurrentTask the subsequent number of the latest submissions the current task is unsuccessful - * @param taskHints all exercise hints in current tasks - * @return the available exercise hints - */ - private Set getAvailableExerciseHintsForTask(Optional subsequentNumberSuccessfulSubmissionsForPreviousTask, - int subsequentNumberOfUnsuccessfulSubmissionsForCurrentTask, Set taskHints) { - Set availableHintsForTask = new HashSet<>(); - for (ExerciseHint hint : taskHints) { - // condition 1 - if (subsequentNumberSuccessfulSubmissionsForPreviousTask.isPresent() && subsequentNumberSuccessfulSubmissionsForPreviousTask.get() < hint.getDisplayThreshold()) { - continue; - } - - // condition 2 - if (subsequentNumberOfUnsuccessfulSubmissionsForCurrentTask >= hint.getDisplayThreshold()) { - availableHintsForTask.add(hint); - } - } - return availableHintsForTask; - } - - /** - * Returns the title of the hint identified by the given hint id if the exercise id stored in the hint matches the - * provided exercise id. - * - * @param exerciseId the exercise id that must match the one stored in the hint - * @param exerciseHintId the id of the hint - * @return the title of the hint if it was found; null otherwise - * @throws ConflictException if the provided exercise id does not match the one stored in the hint - */ - @Cacheable(cacheNames = "exerciseHintTitle", key = "''.concat(#exerciseId).concat('-').concat(#exerciseHintId)", unless = "#result == null") - public String getExerciseHintTitle(Long exerciseId, Long exerciseHintId) { - final var hint = exerciseHintRepository.findByIdElseThrow(exerciseHintId); - if (hint.getExercise() == null || !hint.getExercise().getId().equals(exerciseId)) { - throw new ConflictException("An exercise hint can only be retrieved if the exerciseIds match.", "exerciseHint", "exerciseIdsMismatch"); - } - - return hint.getTitle(); - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/TestwiseCoverageService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/TestwiseCoverageService.java deleted file mode 100644 index 9fd6b6ca59f8..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/TestwiseCoverageService.java +++ /dev/null @@ -1,314 +0,0 @@ -package de.tum.cit.aet.artemis.programming.service.hestia; - -import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; - -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - -import org.eclipse.jgit.api.errors.GitAPIException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.annotation.Profile; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Service; - -import de.tum.cit.aet.artemis.core.exception.InternalServerErrorException; -import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise; -import de.tum.cit.aet.artemis.programming.domain.ProgrammingSubmission; -import de.tum.cit.aet.artemis.programming.domain.SolutionProgrammingExerciseParticipation; -import de.tum.cit.aet.artemis.programming.domain.hestia.CoverageFileReport; -import de.tum.cit.aet.artemis.programming.domain.hestia.CoverageReport; -import de.tum.cit.aet.artemis.programming.domain.hestia.TestwiseCoverageReportEntry; -import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseTestCaseRepository; -import de.tum.cit.aet.artemis.programming.repository.hestia.CoverageFileReportRepository; -import de.tum.cit.aet.artemis.programming.repository.hestia.CoverageReportRepository; -import de.tum.cit.aet.artemis.programming.repository.hestia.TestwiseCoverageReportEntryRepository; -import de.tum.cit.aet.artemis.programming.service.GitService; -import de.tum.cit.aet.artemis.programming.service.RepositoryService; -import de.tum.cit.aet.artemis.programming.service.ci.notification.dto.TestwiseCoverageReportDTO; - -/** - * Service for managing testwise coverage data and interacts with both CoverageReport, CoverageFileReport - * and TestwiseCoverageReportEntry - */ -@Profile(PROFILE_CORE) -@Service -public class TestwiseCoverageService { - - private static final Logger log = LoggerFactory.getLogger(TestwiseCoverageService.class); - - private final CoverageReportRepository coverageReportRepository; - - private final CoverageFileReportRepository coverageFileReportRepository; - - private final TestwiseCoverageReportEntryRepository testwiseCoverageReportEntryRepository; - - private final ProgrammingExerciseTestCaseRepository programmingExerciseTestCaseRepository; - - private final GitService gitService; - - private final RepositoryService repositoryService; - - public TestwiseCoverageService(CoverageReportRepository coverageReportRepository, CoverageFileReportRepository coverageFileReportRepository, - TestwiseCoverageReportEntryRepository testwiseCoverageReportEntryRepository, ProgrammingExerciseTestCaseRepository programmingExerciseTestCaseRepository, - RepositoryService repositoryService, GitService gitService) { - this.coverageReportRepository = coverageReportRepository; - this.coverageFileReportRepository = coverageFileReportRepository; - this.testwiseCoverageReportEntryRepository = testwiseCoverageReportEntryRepository; - this.programmingExerciseTestCaseRepository = programmingExerciseTestCaseRepository; - this.gitService = gitService; - this.repositoryService = repositoryService; - } - - /** - * Transforms the testwise coverage report DTOs to CoverageFileReports (without the test case attribute) mapped by the test case name. - * This method maps the file reports to primitive test case names because the test case is not present in the database - * on creating the entities from the DTOs. - * - * @param coverageReports the coverage report DTOs - * @return coverage file reports mapped by the test case name - */ - public Map> createTestwiseCoverageFileReportsWithoutTestsByTestCaseName(List coverageReports) { - Map> fileReportsByTestName = new HashMap<>(); - coverageReports.forEach(coveragePerTestDTO -> { - - // file reports for the current test case - Set testCoverageReports = new HashSet<>(); - for (var pathDTO : coveragePerTestDTO.getCoveredPathsPerTestDTOs()) { - - // the file reports for the current test case and path - Set fileCoverageReports = new HashSet<>(); - for (var fileDTO : pathDTO.getCoveredFilesPerTestDTOs()) { - var coverageEntriesPerFile = Arrays.stream(fileDTO.getCoveredLinesWithRanges().split(",")).map(optionalLineRange -> { - // The test case is not set for the report because it has not been saved yet to the database - var entry = new TestwiseCoverageReportEntry(); - // retrieve consecutive blocks from the ranged covered lines number - // Example: "2,3-6,7,9-30" - if (optionalLineRange.contains("-")) { - String[] range = optionalLineRange.split("-"); - int startLineNumber = Integer.parseInt(range[0]); - entry.setStartLine(startLineNumber); - entry.setLineCount(Integer.parseInt(range[1]) - startLineNumber + 1); - } - else { - entry.setStartLine(Integer.parseInt(optionalLineRange)); - entry.setLineCount(1); - } - return entry; - }).collect(Collectors.toSet()); - - // build the file report with the entries for this specific file - var fileReport = new CoverageFileReport(); - // 'src/' needs to be prepended to match the repositories' relative file path - String filePath = "src/" + pathDTO.getPath() + "/" + fileDTO.getFileName(); - fileReport.setFilePath(filePath); - fileReport.setTestwiseCoverageEntries(coverageEntriesPerFile); - fileCoverageReports.add(fileReport); - } - testCoverageReports.addAll(fileCoverageReports); - } - - // extract the test case name from the uniformPath - String[] split = coveragePerTestDTO.getUniformPath().split("/"); - String receivedTestCaseName = split[split.length - 1]; - fileReportsByTestName.put(receivedTestCaseName, testCoverageReports); - }); - - return fileReportsByTestName; - } - - /** - * Creates a coverage report from a testwise coverage report. - * Test case names are resolved to a test case of the given programming exercise, adds this reference to the given - * entries and saves the entries with the test case reference to the database. - * In case, no test case could be found for the given name, the report for this test case will not be saved - * - * @param fileReportByTestCaseName a map containing the test case name as a key and the file coverage reports without the - * reference to a test case as a value - * @param submission the solution programming submission for which the report is updated - * @param exercise the exercise for which the report should be updated - */ - public void createTestwiseCoverageReport(Map> fileReportByTestCaseName, ProgrammingExercise exercise, ProgrammingSubmission submission) { - // If the report already exists, do not create a new report. This is the case if the build plan will be re-run - boolean reportAlreadyExists = coverageReportRepository.existsBySubmissionId(submission.getId()); - if (reportAlreadyExists) { - return; - } - - var testCases = programmingExerciseTestCaseRepository.findByExerciseId(exercise.getId()); - var solutionLineCountByFilePath = getLineCountByFilePath(submission); - - // Save the full report with the test case and submission, but without the individual file reports as they do not have an ID yet - var fullReport = new CoverageReport(); - fullReport.setSubmission(submission); - var savedFullReport = coverageReportRepository.save(fullReport); - - // The file reports unique for a file path. the set is used to aggregate multiple file reports from multiple test - // cases into one file report - var uniqueFileReports = new HashSet(); - - fileReportByTestCaseName.forEach((testCaseName, fileReports) -> { - // retrieve the test matching the extracted test case name - var optionalTestCase = testCases.stream() - .filter(testCase -> testCaseName.equals(testCase.getTestName()) || testCaseName.replace("()", "").equals(testCase.getTestName())).findFirst(); - if (optionalTestCase.isEmpty()) { - log.error("No test case with name {} could be found when matching with the testwise coverage", testCaseName); - return; - } - var testCase = optionalTestCase.get(); - - fileReports.forEach(fileReport -> { - // If the file does not exist in the solution repository, no file report will be created - // This is for example the case if the test itself invokes code in a test class - if (solutionLineCountByFilePath.get(fileReport.getFilePath()) == null) { - return; - } - - // Temporarily save the testwise entries for the current file report as the entries do not have - // an ID and therefore cause an exception when the file report is saved to the database - var testwiseEntries = fileReport.getTestwiseCoverageEntries(); - - var optionalReportWithSameName = uniqueFileReports.stream().filter(report -> report.getFilePath().equals(fileReport.getFilePath())).findFirst(); - CoverageFileReport savedFileReport; - - if (optionalReportWithSameName.isEmpty()) { - // Remove the testwise entries temporarily as they do not have an ID yet - fileReport.setTestwiseCoverageEntries(Collections.emptySet()); - fileReport.setFullReport(savedFullReport); - // Save the line count - var lineCount = solutionLineCountByFilePath.get(fileReport.getFilePath()); - fileReport.setLineCount(lineCount); - - savedFileReport = coverageFileReportRepository.save(fileReport); - } - else { - var reportWithSameName = optionalReportWithSameName.get(); - savedFileReport = coverageFileReportRepository.save(reportWithSameName); - } - uniqueFileReports.add(savedFileReport); - - // Save all entries for the current file report to the database - testwiseEntries.forEach(entry -> { - entry.setTestCase(testCase); - entry.setFileReport(savedFileReport); - testwiseCoverageReportEntryRepository.save(entry); - }); - }); - }); - - var updatedFullReport = coverageReportRepository.findCoverageReportByIdWithEagerFileReportsAndEntriesElseThrow(savedFullReport.getId()); - // Calculate the unique line count for all file reports and save this value to the database - var coveredLinesCountByFilePath = calculateAndSaveUniqueLineCountsByFilePath(updatedFullReport); - - // Calculate the aggregated covered line ratio over all files - double aggregatedCoveredLineRatio = calculateAggregatedLineCoverage(solutionLineCountByFilePath, coveredLinesCountByFilePath); - updatedFullReport.setCoveredLineRatio(aggregatedCoveredLineRatio); - coverageReportRepository.save(updatedFullReport); - } - - /** - * Returns the line count by file name for all files in the solution repository for the last submission. - * - * @return line count by file name of files in the last submission's solution repository - */ - private Map getLineCountByFilePath(ProgrammingSubmission submission) { - try { - var solutionParticipation = (SolutionProgrammingExerciseParticipation) submission.getParticipation(); - var solutionRepo = gitService.getOrCheckoutRepository(solutionParticipation.getVcsRepositoryUri(), true); - gitService.resetToOriginHead(solutionRepo); - gitService.pullIgnoreConflicts(solutionRepo); - var solutionFiles = repositoryService.getFilesContentFromWorkingCopy(solutionRepo); - var result = new HashMap(); - solutionFiles.forEach((filePath, value) -> { - // do not count lines for non-java/kotlin files - if (!(filePath.endsWith(".java") || filePath.endsWith(".kt"))) { - return; - } - var lineCount = value.split("\n").length + 1; - result.put(filePath, lineCount); - }); - return result; - } - catch (GitAPIException e) { - log.error("Exception while generating testwise coverage report", e); - throw new InternalServerErrorException("Error while generating testwise coverage report: " + e.getMessage()); - } - } - - /** - * Calculates the aggregated covered line ratio for all file reports. - * - * @param lineCountByFileName the general line count by file name - * @param coveredLineCountByFileName the covered line count by file name - * @return the covered line ratio for all files - */ - private double calculateAggregatedLineCoverage(Map lineCountByFileName, Map coveredLineCountByFileName) { - var aggregatedLineCount = lineCountByFileName.values().stream().mapToInt(Integer::intValue).sum(); - if (aggregatedLineCount == 0) { - return 0; - } - var aggregatedCoveredLineCount = coveredLineCountByFileName.values().stream().mapToInt(Integer::intValue).sum(); - - return aggregatedCoveredLineCount / (double) aggregatedLineCount; - } - - /** - * Calculate the unique covered line count for all file reports and save this value to the database for all - * individual file reports. CoverageFileReports can contain multiple TestwiseCoverageReportEntries referencing - * the same lines, but referencing a different test case. This mapping is still required, but simple summing may - * count the same covered lines multiple times. - * - * @param report the report for which the line counts of its file reports should be calculated and saved - * @return a map with the number of covered lines (value) by file path (key) - */ - private Map calculateAndSaveUniqueLineCountsByFilePath(CoverageReport report) { - var coveredLinesByFilePath = new HashMap(); - report.getFileReports().forEach(fileReport -> { - var lineSet = new HashSet(); - fileReport.getTestwiseCoverageEntries() - .forEach(entry -> lineSet.addAll(IntStream.rangeClosed(entry.getStartLine(), entry.getStartLine() + entry.getLineCount() - 1).boxed().toList())); - fileReport.setCoveredLineCount(lineSet.size()); - coverageFileReportRepository.save(fileReport); - coveredLinesByFilePath.put(fileReport.getFilePath(), lineSet.size()); - }); - return coveredLinesByFilePath; - } - - /** - * Return the test-wise coverage report for the latest solution submission for a programming exercise without the file reports. - * - * @param exerciseId the exercise id for which the latest coverage report should be retrieved - * @return an Optional of the test-wise coverage report for the latest solution submission without the file reports - * if a report exists for the latest submission, otherwise an empty Optional - */ - public Optional getCoverageReportForLatestSolutionSubmissionFromProgrammingExercise(long exerciseId) { - var reports = coverageReportRepository.getLatestCoverageReportsWithLegalSubmissionsForProgrammingExercise(exerciseId, Pageable.ofSize(1)); - if (reports.isEmpty()) { - return Optional.empty(); - } - return Optional.of(reports.getFirst()); - } - - /** - * Return the full test-wise coverage report for the latest solution submission for a programming exercise containing all file reports - * - * @param exerciseId the exercise id for which the latest coverage report should be retrieved - * @return an Optional of the full test-wise coverage report for the latest solution submission with all file reports - * if a report exists for the latest submission, otherwise an empty Optional - */ - public Optional getFullCoverageReportForLatestSolutionSubmissionFromProgrammingExercise(long exerciseId) { - var reports = coverageReportRepository.getLatestCoverageReportsForLegalSubmissionsForProgrammingExerciseWithEagerFileReportsAndEntries(exerciseId, Pageable.ofSize(1)); - if (reports.isEmpty()) { - return Optional.empty(); - } - return Optional.of(reports.getFirst()); - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/behavioral/BehavioralBlackboard.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/behavioral/BehavioralBlackboard.java deleted file mode 100644 index dd1417117e6b..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/behavioral/BehavioralBlackboard.java +++ /dev/null @@ -1,58 +0,0 @@ -package de.tum.cit.aet.artemis.programming.service.hestia.behavioral; - -import java.util.List; -import java.util.Map; - -import de.tum.cit.aet.artemis.programming.domain.hestia.CoverageReport; -import de.tum.cit.aet.artemis.programming.domain.hestia.ProgrammingExerciseGitDiffReport; -import de.tum.cit.aet.artemis.programming.domain.hestia.ProgrammingExerciseSolutionEntry; - -/** - * The blackboard for creating SolutionEntries for behavioral test cases utilizing the git-diff and test-wise coverage report. - */ -public class BehavioralBlackboard { - - private final ProgrammingExerciseGitDiffReport gitDiffReport; - - private final CoverageReport coverageReport; - - private final Map solutionRepoFiles; - - private List groupedFiles; - - private List solutionEntries; - - public BehavioralBlackboard(ProgrammingExerciseGitDiffReport gitDiffReport, CoverageReport coverageReport, Map solutionRepoFiles) { - this.gitDiffReport = gitDiffReport; - this.coverageReport = coverageReport; - this.solutionRepoFiles = solutionRepoFiles; - } - - public ProgrammingExerciseGitDiffReport getGitDiffReport() { - return gitDiffReport; - } - - public CoverageReport getCoverageReport() { - return coverageReport; - } - - public Map getSolutionRepoFiles() { - return solutionRepoFiles; - } - - public List getSolutionEntries() { - return solutionEntries; - } - - public void setSolutionEntries(List solutionEntries) { - this.solutionEntries = solutionEntries; - } - - public List getGroupedFiles() { - return groupedFiles; - } - - public void setGroupedFiles(List groupedFiles) { - this.groupedFiles = groupedFiles; - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/behavioral/BehavioralSolutionEntryGenerationException.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/behavioral/BehavioralSolutionEntryGenerationException.java deleted file mode 100644 index 118662864564..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/behavioral/BehavioralSolutionEntryGenerationException.java +++ /dev/null @@ -1,18 +0,0 @@ -package de.tum.cit.aet.artemis.programming.service.hestia.behavioral; - -/** - * Exception used for the generation of solution entries for behavioral test cases - * It is thrown if there was an error while generating the solution entries - */ -public class BehavioralSolutionEntryGenerationException extends Exception { - - private static final String MESSAGE_PREFIX = "Unable to generate behavioral solution entries: "; - - public BehavioralSolutionEntryGenerationException(String message) { - super(MESSAGE_PREFIX + message); - } - - public BehavioralSolutionEntryGenerationException(String message, Throwable cause) { - super(MESSAGE_PREFIX + message, cause); - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/behavioral/BehavioralTestCaseService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/behavioral/BehavioralTestCaseService.java deleted file mode 100644 index 71df2fdc6690..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/behavioral/BehavioralTestCaseService.java +++ /dev/null @@ -1,201 +0,0 @@ -package de.tum.cit.aet.artemis.programming.service.hestia.behavioral; - -import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -import org.eclipse.jgit.api.errors.GitAPIException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.annotation.Profile; -import org.springframework.stereotype.Service; - -import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise; -import de.tum.cit.aet.artemis.programming.domain.hestia.ProgrammingExerciseSolutionEntry; -import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseTestCaseRepository; -import de.tum.cit.aet.artemis.programming.repository.SolutionProgrammingExerciseParticipationRepository; -import de.tum.cit.aet.artemis.programming.repository.hestia.ProgrammingExerciseSolutionEntryRepository; -import de.tum.cit.aet.artemis.programming.service.GitService; -import de.tum.cit.aet.artemis.programming.service.RepositoryService; -import de.tum.cit.aet.artemis.programming.service.hestia.ProgrammingExerciseGitDiffReportService; -import de.tum.cit.aet.artemis.programming.service.hestia.TestwiseCoverageService; -import de.tum.cit.aet.artemis.programming.service.hestia.behavioral.knowledgesource.AddUncoveredLinesAsPotentialCodeBlocks; -import de.tum.cit.aet.artemis.programming.service.hestia.behavioral.knowledgesource.BehavioralKnowledgeSource; -import de.tum.cit.aet.artemis.programming.service.hestia.behavioral.knowledgesource.CombineChangeBlocks; -import de.tum.cit.aet.artemis.programming.service.hestia.behavioral.knowledgesource.CreateCommonChangeBlocks; -import de.tum.cit.aet.artemis.programming.service.hestia.behavioral.knowledgesource.CreateSolutionEntries; -import de.tum.cit.aet.artemis.programming.service.hestia.behavioral.knowledgesource.DropRemovedGitDiffEntries; -import de.tum.cit.aet.artemis.programming.service.hestia.behavioral.knowledgesource.ExtractChangedLines; -import de.tum.cit.aet.artemis.programming.service.hestia.behavioral.knowledgesource.ExtractCoveredLines; -import de.tum.cit.aet.artemis.programming.service.hestia.behavioral.knowledgesource.FindCommonLines; -import de.tum.cit.aet.artemis.programming.service.hestia.behavioral.knowledgesource.GroupGitDiffAndCoverageEntriesByFilePathAndTestCase; -import de.tum.cit.aet.artemis.programming.service.hestia.behavioral.knowledgesource.InsertFileContents; - -/** - * Service for handling Solution Entries of behavioral Test Cases. - */ -@Profile(PROFILE_CORE) -@Service -public class BehavioralTestCaseService { - - private static final Logger log = LoggerFactory.getLogger(BehavioralTestCaseService.class); - - private final GitService gitService; - - private final RepositoryService repositoryService; - - private final TestwiseCoverageService testwiseCoverageService; - - private final ProgrammingExerciseTestCaseRepository testCaseRepository; - - private final ProgrammingExerciseSolutionEntryRepository solutionEntryRepository; - - private final ProgrammingExerciseGitDiffReportService programmingExerciseGitDiffReportService; - - private final SolutionProgrammingExerciseParticipationRepository solutionProgrammingExerciseParticipationRepository; - - public BehavioralTestCaseService(GitService gitService, RepositoryService repositoryService, TestwiseCoverageService testwiseCoverageService, - ProgrammingExerciseTestCaseRepository testCaseRepository, ProgrammingExerciseSolutionEntryRepository solutionEntryRepository, - ProgrammingExerciseGitDiffReportService programmingExerciseGitDiffReportService, - SolutionProgrammingExerciseParticipationRepository solutionProgrammingExerciseParticipationRepository) { - this.gitService = gitService; - this.repositoryService = repositoryService; - this.testCaseRepository = testCaseRepository; - this.testwiseCoverageService = testwiseCoverageService; - this.solutionEntryRepository = solutionEntryRepository; - this.programmingExerciseGitDiffReportService = programmingExerciseGitDiffReportService; - this.solutionProgrammingExerciseParticipationRepository = solutionProgrammingExerciseParticipationRepository; - } - - /** - * Generates the solution entries for all behavioral test cases of a programming exercise. - * This uses the git-diff report, the testwise coverage, and the test cases of the programming exercise and - * requires them to exist. - * Therefore, this method also requires the exercise to have testwiseCoverageEnabled set to true. - * - * @param programmingExercise The programming exercise - * @return The new behavioral solution entries - * @throws BehavioralSolutionEntryGenerationException If there was an error while generating the solution entries - */ - public List generateBehavioralSolutionEntries(ProgrammingExercise programmingExercise) throws BehavioralSolutionEntryGenerationException { - if (!programmingExercise.getBuildConfig().isTestwiseCoverageEnabled()) { - throw new BehavioralSolutionEntryGenerationException("This feature is only supported for Java Exercises with active Testwise Coverage"); - } - var testCases = testCaseRepository.findByExerciseIdWithSolutionEntriesAndActive(programmingExercise.getId(), true); - if (testCases.isEmpty()) { - throw new BehavioralSolutionEntryGenerationException("Test cases have not been received yet"); - } - var gitDiffReport = programmingExerciseGitDiffReportService.getOrCreateReportOfExercise(programmingExercise); - if (gitDiffReport == null) { - throw new BehavioralSolutionEntryGenerationException("Git-Diff Report has not been generated"); - } - - var coverageReport = testwiseCoverageService.getFullCoverageReportForLatestSolutionSubmissionFromProgrammingExercise(programmingExercise.getId()).orElse(null); - if (coverageReport == null) { - throw new BehavioralSolutionEntryGenerationException("Testwise coverage report has not been generated"); - } - log.info("Generating the behavioral solution entries for programming exercise {}", programmingExercise.getId()); - - var solutionRepoFiles = readSolutionRepo(programmingExercise); - - var blackboard = new BehavioralBlackboard(gitDiffReport, coverageReport, solutionRepoFiles); - applyKnowledgeSources(blackboard); - var newSolutionEntries = blackboard.getSolutionEntries(); - if (newSolutionEntries == null || newSolutionEntries.isEmpty()) { - throw new BehavioralSolutionEntryGenerationException("No solution entry was generated"); - } - // Remove temporary id before saving - for (ProgrammingExerciseSolutionEntry solutionEntry : newSolutionEntries) { - solutionEntry.setId(null); - } - newSolutionEntries = solutionEntryRepository.saveAll(newSolutionEntries); - - // Get all old solution entries - var oldSolutionEntries = newSolutionEntries.stream().map(ProgrammingExerciseSolutionEntry::getTestCase) - .map(testCase1 -> testCases.stream().filter(testCase2 -> Objects.equals(testCase1.getId(), testCase2.getId())).findFirst().orElse(null)).filter(Objects::nonNull) - .flatMap(testCase -> testCase.getSolutionEntries().stream()).distinct().toList(); - - // Save new solution entries - newSolutionEntries = solutionEntryRepository.saveAll(newSolutionEntries); - - // Delete old solution entries - solutionEntryRepository.deleteAll(oldSolutionEntries); - - log.info("{} behavioral solution entries for programming exercise {} have been generated", newSolutionEntries.size(), programmingExercise.getId()); - return newSolutionEntries; - } - - /** - * Utilizing the blackboard pattern this method creates the behavioral solution entries step by step. - * Look at the specific KnowledgeSources to learn more. - * - * @param blackboard The blackboard containing the base information - * @throws BehavioralSolutionEntryGenerationException If there was an error while generating the solution entries - */ - private void applyKnowledgeSources(BehavioralBlackboard blackboard) throws BehavioralSolutionEntryGenerationException { - // Create knowledge sources (Turning the formatter off to make the code more readable) - // @formatter:off - List behavioralKnowledgeSources = Arrays.asList( - new DropRemovedGitDiffEntries(blackboard), - new GroupGitDiffAndCoverageEntriesByFilePathAndTestCase(blackboard), - new ExtractCoveredLines(blackboard), - new ExtractChangedLines(blackboard), - new FindCommonLines(blackboard), - new CreateCommonChangeBlocks(blackboard), - new InsertFileContents(blackboard), - new AddUncoveredLinesAsPotentialCodeBlocks(blackboard), - new CombineChangeBlocks(blackboard), - new CreateSolutionEntries(blackboard) - ); - // @formatter:on - - boolean done = false; - int iterations = 0; - while (!done) { - boolean didChanges = false; - for (BehavioralKnowledgeSource behavioralKnowledgeSource : behavioralKnowledgeSources) { - if (behavioralKnowledgeSource.executeCondition()) { - log.debug("Executing knowledge source {}", behavioralKnowledgeSource.getClass().getSimpleName()); - didChanges = behavioralKnowledgeSource.executeAction() || didChanges; - } - } - done = !didChanges; - iterations++; - // Safeguard to prevent an infinite loop - if (iterations >= 200) { - throw new BehavioralSolutionEntryGenerationException("The creation of the solution entries got stuck and was cancelled"); - } - } - } - - /** - * Reads the contents of all files in the solution repository and returns them mapped with the file path as the key. - * - * @param programmingExercise The programming exercise - * @return The file path of each file mapped to their contents - * @throws BehavioralSolutionEntryGenerationException If there was an error while reading the solution repository - */ - private Map readSolutionRepo(ProgrammingExercise programmingExercise) throws BehavioralSolutionEntryGenerationException { - try { - log.debug("Reading the contents of the solution repository"); - var solutionParticipationOptional = solutionProgrammingExerciseParticipationRepository.findByProgrammingExerciseId(programmingExercise.getId()); - if (solutionParticipationOptional.isEmpty()) { - return Collections.emptyMap(); - } - var solutionParticipation = solutionParticipationOptional.get(); - var solutionRepo = gitService.getOrCheckoutRepository(solutionParticipation.getVcsRepositoryUri(), true); - - gitService.resetToOriginHead(solutionRepo); - gitService.pullIgnoreConflicts(solutionRepo); - - return repositoryService.getFilesContentFromWorkingCopy(solutionRepo); - } - catch (GitAPIException e) { - throw new BehavioralSolutionEntryGenerationException("Error while reading solution repository", e); - } - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/behavioral/GroupedFile.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/behavioral/GroupedFile.java deleted file mode 100644 index 62eab75c4b1f..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/behavioral/GroupedFile.java +++ /dev/null @@ -1,150 +0,0 @@ -package de.tum.cit.aet.artemis.programming.service.hestia.behavioral; - -import java.util.Collection; -import java.util.Objects; -import java.util.Set; -import java.util.SortedSet; -import java.util.TreeSet; - -import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseTestCase; -import de.tum.cit.aet.artemis.programming.domain.hestia.ProgrammingExerciseGitDiffEntry; -import de.tum.cit.aet.artemis.programming.domain.hestia.TestwiseCoverageReportEntry; - -/** - * One GroupedFile groups the {@link ProgrammingExerciseGitDiffEntry}s and {@link TestwiseCoverageReportEntry}s together - * that belong to the same file. For each {@link ProgrammingExerciseTestCase} that covered the file a separate GroupedFile exists. - */ -public class GroupedFile { - - // The path of the file - private final String filePath; - - // The test case that covered something in this file - private final ProgrammingExerciseTestCase testCase; - - // All changes between the template and solution repositories in this file - private final Set gitDiffEntries; - - // All coverage entries of the test case in this file - private final Set coverageReportEntries; - - // The content of this file - private String fileContent; - - // The lines of this file that were changed - private Set changedLines; - - // The lines of this file that were covered by the test case - private Set coveredLines; - - // The lines in this file that were both covered and changed - private SortedSet commonLines; - - // The changes in this file that should be included in the solution entries - private SortedSet commonChanges; - - public GroupedFile(String filePath, ProgrammingExerciseTestCase testCase, Set gitDiffEntries, - Set coverageReportEntries) { - this.filePath = filePath; - this.testCase = testCase; - this.gitDiffEntries = gitDiffEntries; - this.coverageReportEntries = coverageReportEntries; - } - - public String getFilePath() { - return filePath; - } - - public ProgrammingExerciseTestCase getTestCase() { - return testCase; - } - - public Set getGitDiffEntries() { - return gitDiffEntries; - } - - public Set getCoverageReportEntries() { - return coverageReportEntries; - } - - public String getFileContent() { - return fileContent; - } - - public void setFileContent(String fileContent) { - this.fileContent = fileContent; - } - - public Set getChangedLines() { - return changedLines; - } - - public void setChangedLines(Set changedLines) { - this.changedLines = changedLines; - } - - public Set getCoveredLines() { - return coveredLines; - } - - public void setCoveredLines(Set coveredLines) { - this.coveredLines = coveredLines; - } - - public SortedSet getCommonLines() { - return commonLines; - } - - public void setCommonLines(Collection commonLines) { - this.commonLines = new TreeSet<>(commonLines); - } - - public SortedSet getCommonChanges() { - return commonChanges; - } - - public void setCommonChanges(Collection commonChanges) { - this.commonChanges = new TreeSet<>(commonChanges); - } - - @Override - public int hashCode() { - return Objects.hash(filePath, testCase, gitDiffEntries, coverageReportEntries, fileContent, changedLines, coveredLines, commonLines, commonChanges); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - GroupedFile that = (GroupedFile) obj; - return Objects.equals(filePath, that.filePath) && Objects.equals(testCase, that.testCase) && Objects.equals(gitDiffEntries, that.gitDiffEntries) - && Objects.equals(coverageReportEntries, that.coverageReportEntries) && Objects.equals(fileContent, that.fileContent) - && Objects.equals(changedLines, that.changedLines) && Objects.equals(coveredLines, that.coveredLines) && Objects.equals(commonLines, that.commonLines) - && Objects.equals(commonChanges, that.commonChanges); - } - - public record ChangeBlock(SortedSet lines, boolean isPotential) implements Comparable { - - public ChangeBlock(Collection lines) { - this(new TreeSet<>(lines), false); - } - - public ChangeBlock(Collection lines, boolean isPotential) { - this(new TreeSet<>(lines), isPotential); - } - - @Override - public int compareTo(GroupedFile.ChangeBlock other) { - return Integer.compare(this.lines.first(), other.lines.first()); - } - - public boolean intersectsOrTouches(ChangeBlock other) { - return (this.lines.first() > other.lines.first() && this.lines.first() <= other.lines.last() + 1) - || (this.lines.first() < other.lines.first() && this.lines.last() >= other.lines.first() - 1); - } - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/behavioral/knowledgesource/AddUncoveredLinesAsPotentialCodeBlocks.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/behavioral/knowledgesource/AddUncoveredLinesAsPotentialCodeBlocks.java deleted file mode 100644 index 3bacd3966662..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/behavioral/knowledgesource/AddUncoveredLinesAsPotentialCodeBlocks.java +++ /dev/null @@ -1,110 +0,0 @@ -package de.tum.cit.aet.artemis.programming.service.hestia.behavioral.knowledgesource; - -import java.util.TreeSet; -import java.util.regex.Pattern; - -import de.tum.cit.aet.artemis.programming.service.hestia.behavioral.BehavioralBlackboard; -import de.tum.cit.aet.artemis.programming.service.hestia.behavioral.GroupedFile; -import de.tum.cit.aet.artemis.programming.service.hestia.behavioral.GroupedFile.ChangeBlock; - -/** - * For each {@link GroupedFile}: - * There are certain lines that are not covered by Jacoco (which is used for the testwise coverage) that may still be - * relevant to the code. This includes for example the `else` expression and curly braces. - * For each {@link ChangeBlock}: - * Check if there are such lines before or after the ChangeBlock. If there are add the entire prefix/postfix as a - * potential ChangeBlock to the GroupedFile. - */ -public class AddUncoveredLinesAsPotentialCodeBlocks extends BehavioralKnowledgeSource { - - private static final Pattern CURLY_BRACES_PATTERN = Pattern.compile("\\s*[}{]\\s*"); - - private static final Pattern ELSE_PATTERN = Pattern.compile("\\s*}?\\s*else\\s*\\{?\\s*"); - - private static final Pattern EMPTY_LINE_PATTERN = Pattern.compile("\\s*"); - - public AddUncoveredLinesAsPotentialCodeBlocks(BehavioralBlackboard blackboard) { - super(blackboard); - } - - @Override - public boolean executeCondition() { - return blackboard.getGroupedFiles() != null && blackboard.getGroupedFiles().stream().noneMatch(groupedFile -> groupedFile.getFileContent() == null) - && blackboard.getGroupedFiles().stream().noneMatch(groupedFile -> groupedFile.getCommonChanges() == null); - } - - @Override - public boolean executeAction() { - boolean didChanges = false; - - for (GroupedFile groupedFile : blackboard.getGroupedFiles()) { - var newChangeBlocks = new TreeSet(); - - for (ChangeBlock commonChange : groupedFile.getCommonChanges()) { - var firstLine = commonChange.lines().first(); - var potentialPrefix = getPotentialPrefix(firstLine, groupedFile.getFileContent()); - if (potentialPrefix != null) { - newChangeBlocks.add(potentialPrefix); - } - var lastLine = commonChange.lines().last(); - var potentialPostfix = getPotentialPostfix(lastLine, groupedFile.getFileContent()); - if (potentialPostfix != null) { - newChangeBlocks.add(potentialPostfix); - } - } - - if (!newChangeBlocks.isEmpty()) { - groupedFile.getCommonChanges().addAll(newChangeBlocks); - didChanges = true; - } - } - - return didChanges; - } - - private ChangeBlock getPotentialPrefix(int firstLine, String fileContent) { - var potentialLines = new TreeSet(); - var lineContents = fileContent.split("\n"); - // Starting two lines before the first line, as first line is the line in the file which starts at 1 and not 0 - for (int i = firstLine - 2; i >= 0; i--) { - var lineContent = lineContents[i]; - if (doesLineMatch(lineContent)) { - potentialLines.add(i + 1); - } - else { - break; - } - } - if (potentialLines.isEmpty()) { - return null; - } - else { - return new ChangeBlock(potentialLines, true); - } - } - - private ChangeBlock getPotentialPostfix(int lastLine, String fileContent) { - var potentialLines = new TreeSet(); - var lineContents = fileContent.split("\n"); - // Starting at last line instead of lastLine + 1, as last line is the line in the file which starts at 1 and not 0 - for (int i = lastLine; i < lineContents.length; i++) { - var lineContent = lineContents[i]; - if (doesLineMatch(lineContent)) { - potentialLines.add(i + 1); - } - else { - break; - } - } - if (potentialLines.isEmpty()) { - return null; - } - else { - return new ChangeBlock(potentialLines, true); - } - } - - private boolean doesLineMatch(String lineContent) { - return CURLY_BRACES_PATTERN.matcher(lineContent).matches() || ELSE_PATTERN.matcher(lineContent).matches() || EMPTY_LINE_PATTERN.matcher(lineContent).matches(); - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/behavioral/knowledgesource/BehavioralKnowledgeSource.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/behavioral/knowledgesource/BehavioralKnowledgeSource.java deleted file mode 100644 index a96c534a2623..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/behavioral/knowledgesource/BehavioralKnowledgeSource.java +++ /dev/null @@ -1,27 +0,0 @@ -package de.tum.cit.aet.artemis.programming.service.hestia.behavioral.knowledgesource; - -import de.tum.cit.aet.artemis.programming.service.hestia.behavioral.BehavioralBlackboard; -import de.tum.cit.aet.artemis.programming.service.hestia.behavioral.BehavioralSolutionEntryGenerationException; - -public abstract class BehavioralKnowledgeSource { - - protected final BehavioralBlackboard blackboard; - - public BehavioralKnowledgeSource(BehavioralBlackboard blackboard) { - this.blackboard = blackboard; - } - - /** - * Checks if the knowledge source can be applied - * - * @return true if the knowledge source can be applied - */ - public abstract boolean executeCondition(); - - /** - * Applies this knowledge source to the blackboard - * - * @return true if changes were made - */ - public abstract boolean executeAction() throws BehavioralSolutionEntryGenerationException; -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/behavioral/knowledgesource/CombineChangeBlocks.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/behavioral/knowledgesource/CombineChangeBlocks.java deleted file mode 100644 index d7f1b0c8f764..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/behavioral/knowledgesource/CombineChangeBlocks.java +++ /dev/null @@ -1,61 +0,0 @@ -package de.tum.cit.aet.artemis.programming.service.hestia.behavioral.knowledgesource; - -import java.util.ArrayList; -import java.util.TreeSet; - -import de.tum.cit.aet.artemis.programming.service.hestia.behavioral.BehavioralBlackboard; -import de.tum.cit.aet.artemis.programming.service.hestia.behavioral.GroupedFile; - -/** - * For each {@link GroupedFile}: - * Combines the {@link GroupedFile.ChangeBlock}s that have intersecting or directly attaching lines. - * E.g. if you have the block with the lines `1, 2, 3` and another with the lines `4, 5, 6`, these would be combined - * into one block with the lines `1, 2, 3, 4, 5, 6`. - */ -public class CombineChangeBlocks extends BehavioralKnowledgeSource { - - public CombineChangeBlocks(BehavioralBlackboard blackboard) { - super(blackboard); - } - - @Override - public boolean executeCondition() { - return blackboard.getGroupedFiles() != null && blackboard.getGroupedFiles().stream().noneMatch(groupedFile -> groupedFile.getCommonChanges() == null); - } - - @Override - public boolean executeAction() { - boolean didChanges = false; - - for (GroupedFile groupedFile : blackboard.getGroupedFiles()) { - while (true) { - var currentChangeBlocks = new ArrayList<>(groupedFile.getCommonChanges()); - var newChangeBlocks = new ArrayList(); - for (int i = 0; i < currentChangeBlocks.size(); i++) { - var currentChangeBlock = currentChangeBlocks.get(i); - // If this is not the last change block check if it can be merged with the next change block - if (i < currentChangeBlocks.size() - 1) { - var nextChangeBlock = currentChangeBlocks.get(i + 1); - if (currentChangeBlock.intersectsOrTouches(nextChangeBlock)) { - var lines = new TreeSet<>(currentChangeBlock.lines()); - lines.addAll(nextChangeBlock.lines()); - newChangeBlocks.add(new GroupedFile.ChangeBlock(lines)); - // Skip the next change block, as it has already been processed - i++; - continue; - } - } - newChangeBlocks.add(currentChangeBlock); - } - if (!newChangeBlocks.equals(currentChangeBlocks)) { - groupedFile.setCommonChanges(newChangeBlocks); - didChanges = true; - } - else { - break; - } - } - } - return didChanges; - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/behavioral/knowledgesource/CreateCommonChangeBlocks.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/behavioral/knowledgesource/CreateCommonChangeBlocks.java deleted file mode 100644 index 5da770d0d9bb..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/behavioral/knowledgesource/CreateCommonChangeBlocks.java +++ /dev/null @@ -1,64 +0,0 @@ -package de.tum.cit.aet.artemis.programming.service.hestia.behavioral.knowledgesource; - -import java.util.TreeSet; -import java.util.stream.IntStream; - -import de.tum.cit.aet.artemis.programming.service.hestia.behavioral.BehavioralBlackboard; -import de.tum.cit.aet.artemis.programming.service.hestia.behavioral.GroupedFile; - -/** - * For each {@link GroupedFile}: - * Takes the common lines (intersection of covered and changed lines) and creates {@link GroupedFile.ChangeBlock}s - * from them. Each ChangeBlock represents one continuous block of common lines. - */ -public class CreateCommonChangeBlocks extends BehavioralKnowledgeSource { - - public CreateCommonChangeBlocks(BehavioralBlackboard blackboard) { - super(blackboard); - } - - @Override - public boolean executeCondition() { - return blackboard.getGroupedFiles() != null && blackboard.getGroupedFiles().stream().noneMatch(groupedFile -> groupedFile.getCommonLines() == null) - && blackboard.getGroupedFiles().stream().anyMatch(groupedFile -> groupedFile.getCommonChanges() == null); - } - - @Override - public boolean executeAction() { - boolean didChanges = false; - - for (GroupedFile groupedFile : blackboard.getGroupedFiles()) { - var changeBlocks = new TreeSet(); - - Integer previousLine = null; - Integer startLine = null; - int lineCount = 0; - - for (Integer currentLine : groupedFile.getCommonLines()) { - if (startLine == null) { - startLine = currentLine; - } - // Check if this is a new change block - if (previousLine != null && currentLine - 1 > previousLine) { - changeBlocks.add(new GroupedFile.ChangeBlock(IntStream.range(startLine, startLine + lineCount).boxed().toList())); - lineCount = 0; - startLine = currentLine; - } - lineCount++; - previousLine = currentLine; - } - - // Add the last change block if any existA - if (startLine != null) { - changeBlocks.add(new GroupedFile.ChangeBlock(IntStream.range(startLine, startLine + lineCount).boxed().toList())); - } - - if (!changeBlocks.equals(groupedFile.getCommonChanges())) { - groupedFile.setCommonChanges(changeBlocks); - didChanges = true; - } - } - - return didChanges; - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/behavioral/knowledgesource/CreateSolutionEntries.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/behavioral/knowledgesource/CreateSolutionEntries.java deleted file mode 100644 index 90219039cbb1..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/behavioral/knowledgesource/CreateSolutionEntries.java +++ /dev/null @@ -1,57 +0,0 @@ -package de.tum.cit.aet.artemis.programming.service.hestia.behavioral.knowledgesource; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.stream.Collectors; - -import de.tum.cit.aet.artemis.programming.domain.hestia.ProgrammingExerciseSolutionEntry; -import de.tum.cit.aet.artemis.programming.service.hestia.behavioral.BehavioralBlackboard; -import de.tum.cit.aet.artemis.programming.service.hestia.behavioral.GroupedFile; - -/** - * For each {@link GroupedFile}: - * Creates one {@link ProgrammingExerciseSolutionEntry} for each non-potential {@link GroupedFile.ChangeBlock} - */ -public class CreateSolutionEntries extends BehavioralKnowledgeSource { - - public CreateSolutionEntries(BehavioralBlackboard blackboard) { - super(blackboard); - } - - @Override - public boolean executeCondition() { - return blackboard.getGroupedFiles() != null && blackboard.getGroupedFiles().stream().noneMatch(groupedFile -> groupedFile.getCommonChanges() == null); - } - - @Override - public boolean executeAction() { - var solutionEntries = new ArrayList(); - for (GroupedFile groupedFile : blackboard.getGroupedFiles()) { - for (GroupedFile.ChangeBlock changeBlock : groupedFile.getCommonChanges()) { - if (!changeBlock.isPotential()) { - solutionEntries.add(createSolutionEntry(groupedFile, changeBlock)); - } - } - } - if (solutionEntries.equals(blackboard.getSolutionEntries())) { - return false; - } - blackboard.setSolutionEntries(solutionEntries); - return !blackboard.getSolutionEntries().isEmpty(); - } - - private ProgrammingExerciseSolutionEntry createSolutionEntry(GroupedFile groupedFile, GroupedFile.ChangeBlock changeBlock) { - var solutionEntry = new ProgrammingExerciseSolutionEntry(); - // Set temporary id, as equals checks won't work otherwise - solutionEntry.setId(0L); - solutionEntry.setLine(changeBlock.lines().first()); - solutionEntry.setFilePath(groupedFile.getFilePath()); - solutionEntry.setTestCase(groupedFile.getTestCase()); - var fileContent = groupedFile.getFileContent(); - if (fileContent != null) { - var code = Arrays.stream(fileContent.split("\n")).skip(changeBlock.lines().first() - 1).limit(changeBlock.lines().size()).collect(Collectors.joining("\n")); - solutionEntry.setCode(code); - } - return solutionEntry; - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/behavioral/knowledgesource/DropRemovedGitDiffEntries.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/behavioral/knowledgesource/DropRemovedGitDiffEntries.java deleted file mode 100644 index 679565d2c0e6..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/behavioral/knowledgesource/DropRemovedGitDiffEntries.java +++ /dev/null @@ -1,35 +0,0 @@ -package de.tum.cit.aet.artemis.programming.service.hestia.behavioral.knowledgesource; - -import java.util.HashSet; -import java.util.stream.Collectors; - -import de.tum.cit.aet.artemis.programming.domain.hestia.ProgrammingExerciseGitDiffEntry; -import de.tum.cit.aet.artemis.programming.domain.hestia.ProgrammingExerciseGitDiffReport; -import de.tum.cit.aet.artemis.programming.service.hestia.behavioral.BehavioralBlackboard; - -/** - * Remove all {@link ProgrammingExerciseGitDiffEntry} from the - * {@link ProgrammingExerciseGitDiffReport} of the {@link BehavioralBlackboard} - * that represents consecutive blocks of removed code. - * Entries cannot be generated for removed code, therefore we have to drop them from the git diff report of the blackboard. - */ -public class DropRemovedGitDiffEntries extends BehavioralKnowledgeSource { - - public DropRemovedGitDiffEntries(BehavioralBlackboard blackboard) { - super(blackboard); - } - - @Override - public boolean executeCondition() { - return blackboard.getGitDiffReport() != null && blackboard.getGitDiffReport().getEntries() != null - && blackboard.getGitDiffReport().getEntries().stream().anyMatch(entry -> entry.getStartLine() == null || entry.getLineCount() == null); - } - - @Override - public boolean executeAction() { - var nonRemovedEntries = blackboard.getGitDiffReport().getEntries().stream().filter(entry -> entry.getStartLine() != null && entry.getLineCount() != null) - .collect(Collectors.toCollection(HashSet::new)); - blackboard.getGitDiffReport().setEntries(nonRemovedEntries); - return !nonRemovedEntries.isEmpty(); - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/behavioral/knowledgesource/ExtractChangedLines.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/behavioral/knowledgesource/ExtractChangedLines.java deleted file mode 100644 index 3b259d17c21c..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/behavioral/knowledgesource/ExtractChangedLines.java +++ /dev/null @@ -1,35 +0,0 @@ -package de.tum.cit.aet.artemis.programming.service.hestia.behavioral.knowledgesource; - -import java.util.stream.Collectors; -import java.util.stream.IntStream; - -import de.tum.cit.aet.artemis.programming.domain.hestia.ProgrammingExerciseGitDiffEntry; -import de.tum.cit.aet.artemis.programming.service.hestia.behavioral.BehavioralBlackboard; -import de.tum.cit.aet.artemis.programming.service.hestia.behavioral.GroupedFile; - -/** - * For each {@link GroupedFile}: - * Extracts the lines that were changed in the file (of the GroupedFile) from the - * {@link ProgrammingExerciseGitDiffEntry}s - */ -public class ExtractChangedLines extends BehavioralKnowledgeSource { - - public ExtractChangedLines(BehavioralBlackboard blackboard) { - super(blackboard); - } - - @Override - public boolean executeCondition() { - return blackboard.getGroupedFiles() != null && blackboard.getGroupedFiles().stream().anyMatch(groupedFile -> groupedFile.getChangedLines() == null); - } - - @Override - public boolean executeAction() { - for (GroupedFile groupedFile : blackboard.getGroupedFiles()) { - groupedFile.setChangedLines(groupedFile.getGitDiffEntries().stream() - .flatMapToInt(gitDiffEntry -> IntStream.range(gitDiffEntry.getStartLine(), gitDiffEntry.getStartLine() + gitDiffEntry.getLineCount())).boxed() - .collect(Collectors.toSet())); - } - return !blackboard.getGroupedFiles().stream().allMatch(groupedFile -> groupedFile.getChangedLines().isEmpty()); - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/behavioral/knowledgesource/ExtractCoveredLines.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/behavioral/knowledgesource/ExtractCoveredLines.java deleted file mode 100644 index 19894d567205..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/behavioral/knowledgesource/ExtractCoveredLines.java +++ /dev/null @@ -1,36 +0,0 @@ -package de.tum.cit.aet.artemis.programming.service.hestia.behavioral.knowledgesource; - -import java.util.stream.Collectors; -import java.util.stream.IntStream; - -import de.tum.cit.aet.artemis.programming.domain.hestia.TestwiseCoverageReportEntry; -import de.tum.cit.aet.artemis.programming.service.hestia.behavioral.BehavioralBlackboard; -import de.tum.cit.aet.artemis.programming.service.hestia.behavioral.GroupedFile; - -/** - * For each {@link GroupedFile}: - * Extracts the lines that were covered by the test case in the file (both of the GroupedFile) from the - * {@link TestwiseCoverageReportEntry}s - */ -public class ExtractCoveredLines extends BehavioralKnowledgeSource { - - public ExtractCoveredLines(BehavioralBlackboard blackboard) { - super(blackboard); - } - - @Override - public boolean executeCondition() { - return blackboard.getGroupedFiles() != null && blackboard.getGroupedFiles().stream().anyMatch(groupedFile -> groupedFile.getCoveredLines() == null); - } - - @Override - public boolean executeAction() { - for (GroupedFile groupedFile : blackboard.getGroupedFiles()) { - groupedFile.setCoveredLines(groupedFile.getCoverageReportEntries().stream() - .flatMapToInt( - coverageReportEntry -> IntStream.range(coverageReportEntry.getStartLine(), coverageReportEntry.getStartLine() + coverageReportEntry.getLineCount())) - .boxed().collect(Collectors.toSet())); - } - return !blackboard.getGroupedFiles().stream().allMatch(groupedFile -> groupedFile.getCoveredLines().isEmpty()); - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/behavioral/knowledgesource/FindCommonLines.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/behavioral/knowledgesource/FindCommonLines.java deleted file mode 100644 index cf90f0b9af83..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/behavioral/knowledgesource/FindCommonLines.java +++ /dev/null @@ -1,35 +0,0 @@ -package de.tum.cit.aet.artemis.programming.service.hestia.behavioral.knowledgesource; - -import java.util.ArrayList; -import java.util.List; - -import de.tum.cit.aet.artemis.programming.service.hestia.behavioral.BehavioralBlackboard; -import de.tum.cit.aet.artemis.programming.service.hestia.behavioral.GroupedFile; - -/** - * For each {@link GroupedFile}: - * Creates the intersection of the changed lines and the covered lines. - */ -public class FindCommonLines extends BehavioralKnowledgeSource { - - public FindCommonLines(BehavioralBlackboard blackboard) { - super(blackboard); - } - - @Override - public boolean executeCondition() { - return blackboard.getGroupedFiles() != null && blackboard.getGroupedFiles().stream().noneMatch(groupedFile -> groupedFile.getChangedLines() == null) - && blackboard.getGroupedFiles().stream().noneMatch(groupedFile -> groupedFile.getCoveredLines() == null) - && blackboard.getGroupedFiles().stream().anyMatch(groupedFile -> groupedFile.getCommonLines() == null); - } - - @Override - public boolean executeAction() { - for (GroupedFile groupedFile : blackboard.getGroupedFiles()) { - List commonLines = new ArrayList<>(groupedFile.getCoveredLines()); - commonLines.retainAll(groupedFile.getChangedLines()); - groupedFile.setCommonLines(commonLines); - } - return !blackboard.getGroupedFiles().stream().allMatch(groupedFile -> groupedFile.getCommonLines().isEmpty()); - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/behavioral/knowledgesource/GroupGitDiffAndCoverageEntriesByFilePathAndTestCase.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/behavioral/knowledgesource/GroupGitDiffAndCoverageEntriesByFilePathAndTestCase.java deleted file mode 100644 index c04311732a2b..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/behavioral/knowledgesource/GroupGitDiffAndCoverageEntriesByFilePathAndTestCase.java +++ /dev/null @@ -1,70 +0,0 @@ -package de.tum.cit.aet.artemis.programming.service.hestia.behavioral.knowledgesource; - -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.SortedSet; -import java.util.TreeSet; -import java.util.stream.Collectors; - -import de.tum.cit.aet.artemis.programming.domain.hestia.CoverageFileReport; -import de.tum.cit.aet.artemis.programming.domain.hestia.ProgrammingExerciseGitDiffEntry; -import de.tum.cit.aet.artemis.programming.domain.hestia.TestwiseCoverageReportEntry; -import de.tum.cit.aet.artemis.programming.service.hestia.behavioral.BehavioralBlackboard; -import de.tum.cit.aet.artemis.programming.service.hestia.behavioral.GroupedFile; - -/** - * Knowledge source that takes care of creating the {@link GroupedFile}s used by all other knowledge sources. - * These GroupedFiles are created by grouping all coverage entries and git-diff entries together that belong the same file and test case. - */ -public class GroupGitDiffAndCoverageEntriesByFilePathAndTestCase extends BehavioralKnowledgeSource { - - public GroupGitDiffAndCoverageEntriesByFilePathAndTestCase(BehavioralBlackboard blackboard) { - super(blackboard); - } - - @Override - public boolean executeCondition() { - return blackboard.getGroupedFiles() == null; - } - - @Override - public boolean executeAction() { - var gitDiffEntriesPerFile = blackboard.getGitDiffReport().getEntries().stream() - .collect(Collectors.toMap(ProgrammingExerciseGitDiffEntry::getFilePath, Collections::singleton, (set1, set2) -> { - var entries = new HashSet<>(set1); - entries.addAll(set2); - return entries; - })); - var coverageEntriesPerFile = blackboard.getCoverageReport().getFileReports().stream() - .collect(Collectors.toMap(CoverageFileReport::getFilePath, CoverageFileReport::getTestwiseCoverageEntries)); - - var commonFilePaths = new TreeSet<>(gitDiffEntriesPerFile.keySet()); - commonFilePaths.retainAll(coverageEntriesPerFile.keySet()); - - List groupedFiles = createGroupedFiles(gitDiffEntriesPerFile, coverageEntriesPerFile, commonFilePaths); - if (groupedFiles.isEmpty()) { - return false; - } - else { - blackboard.setGroupedFiles(groupedFiles); - return true; - } - } - - private List createGroupedFiles(Map> gitDiffEntriesPerFile, - Map> coverageEntriesPerFile, SortedSet commonFilePaths) { - return commonFilePaths.stream().flatMap(filePath -> { - var gitDiffEntries = gitDiffEntriesPerFile.get(filePath); - var coverageReportEntries = coverageEntriesPerFile.get(filePath); - return coverageReportEntries.stream().collect(Collectors.toMap(TestwiseCoverageReportEntry::getTestCase, Collections::singleton, (set1, set2) -> { - var entries = new HashSet<>(set1); - entries.addAll(set2); - return entries; - })).entrySet().stream().map(entry -> new GroupedFile(filePath, entry.getKey(), gitDiffEntries, entry.getValue())); - }).toList(); - } - -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/behavioral/knowledgesource/InsertFileContents.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/behavioral/knowledgesource/InsertFileContents.java deleted file mode 100644 index 43296df40286..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/behavioral/knowledgesource/InsertFileContents.java +++ /dev/null @@ -1,34 +0,0 @@ -package de.tum.cit.aet.artemis.programming.service.hestia.behavioral.knowledgesource; - -import de.tum.cit.aet.artemis.programming.service.hestia.behavioral.BehavioralBlackboard; -import de.tum.cit.aet.artemis.programming.service.hestia.behavioral.BehavioralSolutionEntryGenerationException; -import de.tum.cit.aet.artemis.programming.service.hestia.behavioral.GroupedFile; - -/** - * For each {@link GroupedFile}: - * Inserts the contents of the file into the GroupedFile using the filePath. - */ -public class InsertFileContents extends BehavioralKnowledgeSource { - - public InsertFileContents(BehavioralBlackboard blackboard) { - super(blackboard); - } - - @Override - public boolean executeCondition() { - return blackboard.getGroupedFiles() != null && blackboard.getGroupedFiles().stream().anyMatch(groupedFile -> groupedFile.getFileContent() == null); - } - - @Override - public boolean executeAction() throws BehavioralSolutionEntryGenerationException { - for (GroupedFile groupedFile : blackboard.getGroupedFiles()) { - var fileContent = blackboard.getSolutionRepoFiles().get(groupedFile.getFilePath()); - if (fileContent == null) { - throw new BehavioralSolutionEntryGenerationException( - String.format("Unable to find file '%s' in the solution repo despite it being referenced in the git-diff and coverage", groupedFile.getFilePath())); - } - groupedFile.setFileContent(fileContent); - } - return !blackboard.getGroupedFiles().isEmpty(); - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/structural/StructuralAttribute.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/structural/StructuralAttribute.java deleted file mode 100644 index e1b776cf2b1e..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/structural/StructuralAttribute.java +++ /dev/null @@ -1,79 +0,0 @@ -package de.tum.cit.aet.artemis.programming.service.hestia.structural; - -import java.util.ArrayList; -import java.util.List; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.thoughtworks.qdox.model.JavaClass; -import com.thoughtworks.qdox.model.JavaField; - -/** - * Element of the test.json file representing the properties of an attribute of a class - * Used for the generation of solution entries for structural test cases - */ -@JsonIgnoreProperties(ignoreUnknown = true) -class StructuralAttribute implements StructuralElement { - - @JsonProperty(required = true) - private String name; - - @JsonProperty(required = true) - private String type; - - private List modifiers = new ArrayList<>(); - - private List annotations = new ArrayList<>(); - - @Override - public String getSourceCode(StructuralClassElements structuralClassElements, JavaClass solutionClass) { - JavaField solutionAttribute = getSolutionAttribute(solutionClass); - String attributeCode = ""; - if (!this.getAnnotations().isEmpty()) { - attributeCode += getAnnotationsString(this.getAnnotations(), solutionAttribute); - } - if (!this.getModifiers().isEmpty()) { - attributeCode += formatModifiers(this.getModifiers()) + " "; - } - attributeCode += (solutionAttribute == null ? this.getType() : solutionAttribute.getType().getGenericValue()) + " "; - attributeCode += this.getName(); - attributeCode += ";"; - return attributeCode; - } - - private JavaField getSolutionAttribute(JavaClass solutionClass) { - return solutionClass == null ? null : solutionClass.getFields().stream().filter(field -> field.getName().equals(this.getName())).findFirst().orElse(null); - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public List getModifiers() { - return modifiers; - } - - public void setModifiers(List modifiers) { - this.modifiers = modifiers; - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - public List getAnnotations() { - return annotations; - } - - public void setAnnotations(List annotations) { - this.annotations = annotations; - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/structural/StructuralClass.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/structural/StructuralClass.java deleted file mode 100644 index 4cea7c67c954..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/structural/StructuralClass.java +++ /dev/null @@ -1,145 +0,0 @@ -package de.tum.cit.aet.artemis.programming.service.hestia.structural; - -import java.util.ArrayList; -import java.util.List; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.thoughtworks.qdox.model.JavaClass; - -/** - * Element of the test.json file representing the properties of a class - * Used for the generation of solution entries for structural test cases - */ -@JsonIgnoreProperties(ignoreUnknown = true) -class StructuralClass implements StructuralElement { - - @JsonProperty(required = true) - private String name; - - @JsonProperty(value = "package", required = true) - private String packageName; - - private String superclass; - - private List modifiers = new ArrayList<>(); - - @JsonProperty(defaultValue = "false") - private boolean isInterface; - - @JsonProperty(defaultValue = "false") - private boolean isEnum; - - private List interfaces = new ArrayList<>(); - - private List annotations = new ArrayList<>(); - - @Override - public String getSourceCode(StructuralClassElements structuralClassElements, JavaClass solutionClass) { - String classSolutionCode = "package " + this.getPackageName() + ";\n\n"; - - if (!this.getAnnotations().isEmpty()) { - classSolutionCode += getAnnotationsString(this.getAnnotations(), solutionClass); - } - classSolutionCode += getClassHeaderCode(solutionClass); - // Class Body - classSolutionCode += "{\n"; - classSolutionCode += SINGLE_INDENTATION; - if (this.isEnum()) { - classSolutionCode += String.join(", ", structuralClassElements.getEnumValues()); - } - classSolutionCode += "\n}"; - - return classSolutionCode; - } - - private String getClassHeaderCode(JavaClass solutionClass) { - String classHeaderCode = ""; - if (!this.getModifiers().isEmpty()) { - classHeaderCode += formatModifiers(this.getModifiers()) + " "; - } - classHeaderCode += (this.isInterface() ? "interface" : (this.isEnum() ? "enum" : "class")) + " "; - classHeaderCode += this.getName(); - if (solutionClass != null && !solutionClass.getTypeParameters().isEmpty()) { - classHeaderCode += getGenericTypesString(solutionClass.getTypeParameters()); - } - classHeaderCode += " "; - classHeaderCode += getInheritanceCode(); - return classHeaderCode; - } - - private String getInheritanceCode() { - String inheritanceCode = ""; - if (this.getSuperclass() != null) { - inheritanceCode += "extends " + this.getSuperclass() + " "; - } - if (!this.getInterfaces().isEmpty()) { - inheritanceCode += "implements " + String.join(", ", this.getInterfaces()) + " "; - } - return inheritanceCode; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getPackageName() { - return packageName; - } - - public void setPackageName(String packageName) { - this.packageName = packageName; - } - - public String getSuperclass() { - return superclass; - } - - public void setSuperclass(String superclass) { - this.superclass = superclass; - } - - public boolean isInterface() { - return isInterface; - } - - public void setIsInterface(boolean anInterface) { - isInterface = anInterface; - } - - public boolean isEnum() { - return isEnum; - } - - public void setIsEnum(boolean anEnum) { - isEnum = anEnum; - } - - public List getInterfaces() { - return interfaces; - } - - public void setInterfaces(List interfaces) { - this.interfaces = interfaces; - } - - public List getModifiers() { - return modifiers; - } - - public void setModifiers(List modifiers) { - this.modifiers = modifiers; - } - - public List getAnnotations() { - return annotations; - } - - public void setAnnotations(List annotations) { - this.annotations = annotations; - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/structural/StructuralClassElements.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/structural/StructuralClassElements.java deleted file mode 100644 index a5d6535b5090..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/structural/StructuralClassElements.java +++ /dev/null @@ -1,66 +0,0 @@ -package de.tum.cit.aet.artemis.programming.service.hestia.structural; - -import java.util.ArrayList; -import java.util.List; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * Root element of the test.json file - * Used for the generation of solution entries for structural test cases - */ -@JsonIgnoreProperties(ignoreUnknown = true) -public class StructuralClassElements { - - @JsonProperty(value = "class", required = true) - private StructuralClass structuralClass; - - private List methods = new ArrayList<>(); - - private List attributes = new ArrayList<>(); - - private List constructors = new ArrayList<>(); - - private List enumValues = new ArrayList<>(); - - public StructuralClass getStructuralClass() { - return structuralClass; - } - - public void setStructuralClass(StructuralClass structuralClass) { - this.structuralClass = structuralClass; - } - - public List getMethods() { - return methods; - } - - public void setMethods(List methods) { - this.methods = methods; - } - - public List getAttributes() { - return attributes; - } - - public void setAttributes(List attributes) { - this.attributes = attributes; - } - - public List getConstructors() { - return constructors; - } - - public void setConstructors(List constructors) { - this.constructors = constructors; - } - - public List getEnumValues() { - return enumValues; - } - - public void setEnumValues(List enumValues) { - this.enumValues = enumValues; - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/structural/StructuralConstructor.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/structural/StructuralConstructor.java deleted file mode 100644 index 6f01793c4595..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/structural/StructuralConstructor.java +++ /dev/null @@ -1,77 +0,0 @@ -package de.tum.cit.aet.artemis.programming.service.hestia.structural; - -import java.util.ArrayList; -import java.util.List; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.thoughtworks.qdox.model.JavaClass; -import com.thoughtworks.qdox.model.JavaConstructor; - -/** - * Element of the test.json file representing the properties of a constructor of a class - * Used for the generation of solution entries for structural test cases - */ -@JsonIgnoreProperties(ignoreUnknown = true) -class StructuralConstructor implements StructuralElement { - - private List modifiers = new ArrayList<>(); - - private List parameters = new ArrayList<>(); - - private List annotations = new ArrayList<>(); - - @Override - public String getSourceCode(StructuralClassElements structuralClassElements, JavaClass solutionClass) { - JavaConstructor solutionConstructor = getSolutionConstructor(solutionClass); - String constructorSolutionCode = ""; - if (!this.getAnnotations().isEmpty()) { - constructorSolutionCode += getAnnotationsString(this.getAnnotations(), solutionConstructor); - } - if (!this.getModifiers().isEmpty()) { - constructorSolutionCode += formatModifiers(this.getModifiers()) + " "; - } - constructorSolutionCode += structuralClassElements.getStructuralClass().getName(); - constructorSolutionCode += generateParametersString(this.getParameters(), solutionConstructor) + " "; - constructorSolutionCode += "{\n" + SINGLE_INDENTATION + "\n}"; - return constructorSolutionCode; - } - - /** - * Extracts the parameters from a constructor - * - * @param solutionClass The QDox class instance - * @return The parameters of the constructor - */ - private JavaConstructor getSolutionConstructor(JavaClass solutionClass) { - if (solutionClass == null) { - return null; - } - return solutionClass.getConstructors().stream() - .filter(javaConstructor -> doParametersMatch(this.getParameters(), javaConstructor.getParameters(), solutionClass.getTypeParameters())).findFirst().stream() - .findFirst().orElse(null); - } - - public List getModifiers() { - return modifiers; - } - - public void setModifiers(List modifiers) { - this.modifiers = modifiers; - } - - public List getParameters() { - return parameters; - } - - public void setParameters(List parameters) { - this.parameters = parameters; - } - - public List getAnnotations() { - return annotations; - } - - public void setAnnotations(List annotations) { - this.annotations = annotations; - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/structural/StructuralElement.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/structural/StructuralElement.java deleted file mode 100644 index c62e54e7c02b..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/structural/StructuralElement.java +++ /dev/null @@ -1,135 +0,0 @@ -package de.tum.cit.aet.artemis.programming.service.hestia.structural; - -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -import com.thoughtworks.qdox.model.JavaAnnotatedElement; -import com.thoughtworks.qdox.model.JavaClass; -import com.thoughtworks.qdox.model.JavaExecutable; -import com.thoughtworks.qdox.model.JavaGenericDeclaration; -import com.thoughtworks.qdox.model.JavaParameter; -import com.thoughtworks.qdox.model.JavaType; -import com.thoughtworks.qdox.model.JavaTypeVariable; - -public interface StructuralElement { - - String SINGLE_INDENTATION = " "; - - /** - * Generates well formatted Java code for a structural element - * - * @param structuralClassElements The elements of the class from the test.json - * @param solutionClass The class read by QDox - * @return The code for the element - */ - String getSourceCode(StructuralClassElements structuralClassElements, JavaClass solutionClass); - - /** - * Generates the code for annotations that are required by the test.json file. - * Annotations that are present in the source code, but not in the test.json file will be excluded. - * - * @param structuralAnnotations The annotation names from the test.json - * @param annotatedElement The annotated element (e.g. method) read by QDox - * @return The code for the annotations - */ - default String getAnnotationsString(List structuralAnnotations, JavaAnnotatedElement annotatedElement) { - if (annotatedElement == null) { - return String.join("\n", structuralAnnotations) + "\n"; - } - else { - Set structuralAnnotationsSet = new HashSet<>(structuralAnnotations); - return annotatedElement.getAnnotations().stream() - .filter(solutionAnnotation -> structuralAnnotationsSet.contains(solutionAnnotation.getType().getSimpleName()) - || "Override".equals(solutionAnnotation.getType().getSimpleName())) - .map(annotation -> annotation.getCodeBlock().replace(annotation.getType().getGenericCanonicalName(), annotation.getType().getSimpleName())) - .collect(Collectors.joining()); - } - } - - /** - * Creates the String representation of a generics declaration - * - * @param typeParameters The generic type parameters - * @return The String representation - */ - default String getGenericTypesString(List> typeParameters) { - return "<" + typeParameters.stream().map(JavaType::getGenericValue).map(type -> type.substring(1, type.length() - 1)).collect(Collectors.joining(", ")) + ">"; - } - - /** - * Formats the modifiers properly. - * Currently, it only removes the 'optional: ' tags and joins them together. - * - * @param modifiers The modifiers array - * @return The formatted modifiers - */ - default String formatModifiers(List modifiers) { - if (modifiers == null) { - return ""; - } - return modifiers.stream().map(modifier -> modifier.replace("optional: ", "")).collect(Collectors.joining(" ")); - } - - /** - * Checks if the parameters from the source files and those from the test.json match. - * This is used for methods and constructor parameters. - * Contains special handling for generics - * - * @param parameters The parameters from the test.json file - * @param solutionParameters The parameters from the source code - * @param genericDeclarations The current generic declarations - * @return false if any parameter does not match - */ - default boolean doParametersMatch(List parameters, List solutionParameters, List> genericDeclarations) { - if (parameters == null) { - return solutionParameters.isEmpty(); - } - if (parameters.size() != solutionParameters.size()) { - return false; - } - for (int i = 0; i < parameters.size(); i++) { - var typeMatches = parameters.get(i).equals(solutionParameters.get(i).getType().getValue()); - var isGeneric = false; - for (JavaTypeVariable type : genericDeclarations) { - if (type.getName().equals(solutionParameters.get(i).getType().getValue()) || (type.getName() + "[]").equals(solutionParameters.get(i).getType().getValue())) { - isGeneric = true; - break; - } - } - if (!typeMatches && !isGeneric) { - return false; - } - } - return true; - } - - /** - * Generates the string representing the source code of a parameter list - * - * @param parameterTypes The parameters from the test.json file - * @param javaExecutable The executable (e.g. method) read by QDox to take the parameters from - * @return The parameter source code - */ - default String generateParametersString(List parameterTypes, JavaExecutable javaExecutable) { - List solutionParameters = javaExecutable != null ? javaExecutable.getParameters() : Collections.emptyList(); - String result = "("; - if (parameterTypes != null) { - for (int i = 0; i < parameterTypes.size(); i++) { - if (solutionParameters.size() > i) { - // Use original parameter names - parameterTypes.set(i, solutionParameters.get(i).getType().getGenericValue() + " " + solutionParameters.get(i).getName()); - } - else { - // Use var[i] as a fallback - parameterTypes.set(i, parameterTypes.get(i) + " var" + i); - } - } - result += String.join(", ", parameterTypes); - } - result += ")"; - return result; - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/structural/StructuralMethod.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/structural/StructuralMethod.java deleted file mode 100644 index 66fa6eed3bee..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/structural/StructuralMethod.java +++ /dev/null @@ -1,140 +0,0 @@ -package de.tum.cit.aet.artemis.programming.service.hestia.structural; - -import java.util.ArrayList; -import java.util.List; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.google.common.collect.Lists; -import com.thoughtworks.qdox.model.JavaClass; -import com.thoughtworks.qdox.model.JavaMethod; - -/** - * Element of the test.json file representing the properties of a method of a class - * Used for the generation of solution entries for structural test cases - */ -@JsonIgnoreProperties(ignoreUnknown = true) -class StructuralMethod implements StructuralElement { - - @JsonProperty(required = true) - private String name; - - private List modifiers = new ArrayList<>(); - - private List parameters = new ArrayList<>(); - - private List annotations = new ArrayList<>(); - - @JsonProperty(defaultValue = "void") - private String returnType; - - @Override - public String getSourceCode(StructuralClassElements structuralClassElements, JavaClass solutionClass) { - JavaMethod solutionMethod = getSolutionMethod(solutionClass); - String methodSolutionCode = ""; - boolean isAbstract = this.getModifiers().contains("abstract"); - - if (!this.getAnnotations().isEmpty()) { - methodSolutionCode += getAnnotationsString(this.getAnnotations(), solutionMethod); - } - - methodSolutionCode += formatModifiers(structuralClassElements, isAbstract); - - // Generics - if (solutionMethod != null && !solutionMethod.getTypeParameters().isEmpty()) { - methodSolutionCode += getGenericTypesString(solutionMethod.getTypeParameters()) + " "; - } - - // Return type - methodSolutionCode += solutionMethod != null ? solutionMethod.getReturnType().getGenericValue() + " " : this.getReturnType() + " "; - // Name - methodSolutionCode += this.getName(); - // Parameters - methodSolutionCode += generateParametersString(this.getParameters(), solutionMethod); - // Body - methodSolutionCode += isAbstract ? ";" : " {\n" + SINGLE_INDENTATION + "\n}"; - - return methodSolutionCode; - } - - /** - * Formats the modifiers of this method. - * - * @param structuralClassElements The elements of the class from the test.json - * @param isAbstract Whether the method is abstract - * @return The modifiers as Java code - */ - private String formatModifiers(StructuralClassElements structuralClassElements, boolean isAbstract) { - var modifiers = Lists.newArrayList(this.getModifiers()); - // Adjust modifiers for interfaces - if (structuralClassElements.getStructuralClass().isInterface()) { - if (isAbstract) { - modifiers.remove("abstract"); - } - else { - modifiers.addFirst("default"); - } - } - if (!modifiers.isEmpty()) { - return formatModifiers(modifiers) + " "; - } - return ""; - } - - /** - * Finds the QDox method in a given class by its test.json specification - * - * @param solutionClass The QDox class instance - * @return The QDox method instance or null if not found - */ - private JavaMethod getSolutionMethod(JavaClass solutionClass) { - if (solutionClass == null) { - return null; - } - return solutionClass.getMethods().stream().filter(javaMethod -> javaMethod.getName().equals(this.getName())).filter(javaMethod -> { - var genericTypes = new ArrayList<>(solutionClass.getTypeParameters()); - genericTypes.addAll(javaMethod.getTypeParameters()); - return doParametersMatch(this.getParameters(), javaMethod.getParameters(), genericTypes); - }).findFirst().orElse(null); - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public List getModifiers() { - return modifiers; - } - - public void setModifiers(List modifiers) { - this.modifiers = modifiers; - } - - public List getParameters() { - return parameters; - } - - public void setParameters(List parameters) { - this.parameters = parameters; - } - - public List getAnnotations() { - return annotations; - } - - public void setAnnotations(List annotations) { - this.annotations = annotations; - } - - public String getReturnType() { - return returnType; - } - - public void setReturnType(String returnType) { - this.returnType = returnType; - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/structural/StructuralSolutionEntryGenerationException.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/structural/StructuralSolutionEntryGenerationException.java deleted file mode 100644 index 3b9614b9e2aa..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/structural/StructuralSolutionEntryGenerationException.java +++ /dev/null @@ -1,18 +0,0 @@ -package de.tum.cit.aet.artemis.programming.service.hestia.structural; - -/** - * Exception used for the generation of solution entries for structural test cases - * It is thrown if there was an error while generating the solution entries - */ -public class StructuralSolutionEntryGenerationException extends Exception { - - private static final String MESSAGE_PREFIX = "Unable to generate structural solution entries: "; - - public StructuralSolutionEntryGenerationException(String message) { - super(MESSAGE_PREFIX + message); - } - - public StructuralSolutionEntryGenerationException(String message, Throwable cause) { - super(MESSAGE_PREFIX + message, cause); - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/structural/StructuralTestCaseService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/structural/StructuralTestCaseService.java deleted file mode 100644 index 889405573cff..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/hestia/structural/StructuralTestCaseService.java +++ /dev/null @@ -1,254 +0,0 @@ -package de.tum.cit.aet.artemis.programming.service.hestia.structural; - -import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; - -import java.io.IOException; -import java.net.URISyntaxException; -import java.nio.file.FileSystems; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.eclipse.jgit.api.errors.GitAPIException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.annotation.Profile; -import org.springframework.stereotype.Service; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.thoughtworks.qdox.JavaProjectBuilder; -import com.thoughtworks.qdox.model.JavaClass; -import com.thoughtworks.qdox.model.JavaType; - -import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise; -import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseTestCase; -import de.tum.cit.aet.artemis.programming.domain.Repository; -import de.tum.cit.aet.artemis.programming.domain.hestia.ProgrammingExerciseSolutionEntry; -import de.tum.cit.aet.artemis.programming.domain.hestia.ProgrammingExerciseTestCaseType; -import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseTestCaseRepository; -import de.tum.cit.aet.artemis.programming.repository.SolutionProgrammingExerciseParticipationRepository; -import de.tum.cit.aet.artemis.programming.repository.hestia.ProgrammingExerciseSolutionEntryRepository; -import de.tum.cit.aet.artemis.programming.service.GitService; - -/** - * Service for handling Solution Entries of structural Test Cases. - */ -@Profile(PROFILE_CORE) -@Service -public class StructuralTestCaseService { - - private static final Logger log = LoggerFactory.getLogger(StructuralTestCaseService.class); - - private final GitService gitService; - - private final ProgrammingExerciseTestCaseRepository testCaseRepository; - - private final ProgrammingExerciseSolutionEntryRepository solutionEntryRepository; - - private final SolutionProgrammingExerciseParticipationRepository solutionProgrammingExerciseParticipationRepository; - - public StructuralTestCaseService(GitService gitService, ProgrammingExerciseTestCaseRepository testCaseRepository, - ProgrammingExerciseSolutionEntryRepository solutionEntryRepository, - SolutionProgrammingExerciseParticipationRepository solutionProgrammingExerciseParticipationRepository) { - this.gitService = gitService; - this.testCaseRepository = testCaseRepository; - this.solutionEntryRepository = solutionEntryRepository; - this.solutionProgrammingExerciseParticipationRepository = solutionProgrammingExerciseParticipationRepository; - } - - /** - * Generates the solution entries for all structural test cases of a programming exercise. - * This includes solution entries for classes, attributes, methods and constructors. - * - * @param programmingExercise The programming exercise - * @return an unmodifiable list of new structural solution entries - * @throws StructuralSolutionEntryGenerationException If there was an error while generating the solution entries - */ - public List generateStructuralSolutionEntries(ProgrammingExercise programmingExercise) throws StructuralSolutionEntryGenerationException { - log.debug("Generating the structural SolutionEntries for the following programmingExercise: {} {}", programmingExercise.getId(), programmingExercise.getProjectName()); - - var testCases = testCaseRepository.findByExerciseIdWithSolutionEntriesAndActive(programmingExercise.getId(), true); - testCases.removeIf(testCase -> testCase.getType() != ProgrammingExerciseTestCaseType.STRUCTURAL); - - // No test cases = no solution entries needed - if (testCases.isEmpty()) { - return Collections.emptyList(); - } - - // Checkout the solution and test repositories - Repository solutionRepository; - Repository testRepository; - try { - var solutionParticipation = solutionProgrammingExerciseParticipationRepository.findByProgrammingExerciseId(programmingExercise.getId()); - if (solutionParticipation.isEmpty()) { - return Collections.emptyList(); - } - solutionRepository = gitService.getOrCheckoutRepository(solutionParticipation.get().getVcsRepositoryUri(), true); - testRepository = gitService.getOrCheckoutRepository(programmingExercise.getVcsTestRepositoryUri(), true); - - gitService.resetToOriginHead(solutionRepository); - gitService.pullIgnoreConflicts(solutionRepository); - gitService.resetToOriginHead(testRepository); - gitService.pullIgnoreConflicts(testRepository); - } - catch (GitAPIException e) { - var error = "Error while checking out repositories"; - log.error(error, e); - throw new StructuralSolutionEntryGenerationException(error, e); - } - - var classElements = readStructureOracleFile(testRepository.getLocalPath()); - var solutionClasses = getClassesFromFiles(retrieveJavaSourceFiles(solutionRepository.getLocalPath())); - - // Create new solution entries - List newSolutionEntries = generateStructuralSolutionEntries(testCases, solutionRepository, classElements, solutionClasses); - - // Get all old solution entries - var oldSolutionEntries = newSolutionEntries.stream().map(ProgrammingExerciseSolutionEntry::getTestCase).flatMap(testCase -> testCase.getSolutionEntries().stream()) - .distinct().toList(); - - // Save new solution entries - newSolutionEntries = solutionEntryRepository.saveAll(newSolutionEntries); - - // Delete old solution entries - solutionEntryRepository.deleteAll(oldSolutionEntries); - - return newSolutionEntries; - } - - /** - * Private method that takes care of the actual generation of structural solution entries. - * - * @param testCases The test cases of the programming exercise - * @param solutionRepository The solution repository of the programming exercise - * @param classElements The entries from the test.json file - * @param solutionClasses The classes read with QDox - * @return an unmodifiable list of new structural solution entries - */ - private List generateStructuralSolutionEntries(Set testCases, Repository solutionRepository, - StructuralClassElements[] classElements, Map solutionClasses) { - return Arrays.stream(classElements).flatMap(classElement -> { - var packageName = classElement.getStructuralClass().getPackageName(); - var name = classElement.getStructuralClass().getName(); - var solutionClass = solutionClasses.get(packageName + "." + name); - String filePath = "src/" + packageName.replaceAll("\\.", "/") + "/" + name + ".java"; - if (solutionClass != null) { - try { - filePath = solutionRepository.getLocalPath().toAbsolutePath().toUri().relativize(solutionClass.getSource().getURL().toURI()).toString(); - } - catch (URISyntaxException e) { - log.warn("Unable to create file path for class", e); - } - } - - String classSolutionCode = classElement.getStructuralClass().getSourceCode(classElement, solutionClass); - List constructorsSolutionCode = classElement.getConstructors().stream().map(constructor -> constructor.getSourceCode(classElement, solutionClass)).toList(); - List methodsSolutionCode = classElement.getMethods().stream().map(method -> method.getSourceCode(classElement, solutionClass)).toList(); - List attributesSolutionCode = classElement.getAttributes().stream().map(attribute -> attribute.getSourceCode(classElement, solutionClass)).toList(); - return Stream.of(createSolutionEntry(filePath, classSolutionCode, findStructuralTestCase("Class", name, testCases)), - createSolutionEntry(filePath, String.join("\n\n", attributesSolutionCode), findStructuralTestCase("Attributes", name, testCases)), - createSolutionEntry(filePath, String.join("\n\n", constructorsSolutionCode), findStructuralTestCase("Constructors", name, testCases)), - createSolutionEntry(filePath, String.join("\n\n", methodsSolutionCode), findStructuralTestCase("Methods", name, testCases))); - }).filter(Objects::nonNull).toList(); - } - - /** - * Finds a structural test case of a specific type and class in the list of all test cases of an exercise. - * - * @param type The type of the structural test case (e.g. Class, Methods) - * @param className The name of the Class - * @param testCases The list of test cases - * @return The matching test case or empty in none found - */ - private Optional findStructuralTestCase(String type, String className, Set testCases) { - return testCases.stream().filter(testCase -> testCase.getTestName().equals("test" + type + "[" + className + "]")).findFirst(); - } - - /** - * Helper method for creating a solution entry. - * If the given test case is not present this will return null. - * - * @param filePath The filePath of the solution entry - * @param code The code of the solution entry - * @param testCase The test case of the solution entry - * @return A SolutionEntry if testCase is present otherwise null - */ - private ProgrammingExerciseSolutionEntry createSolutionEntry(String filePath, String code, Optional testCase) { - return testCase.map(actualTestCase -> { - var solutionEntry = new ProgrammingExerciseSolutionEntry(); - solutionEntry.setFilePath(filePath); - solutionEntry.setPreviousLine(null); - solutionEntry.setPreviousCode(null); - solutionEntry.setLine(1); - solutionEntry.setCode(code); - solutionEntry.setTestCase(actualTestCase); - return solutionEntry; - }).orElse(null); - } - - /** - * Finds, reads and parses the test.json file from the test repository - * - * @param testRepoPath The base path of the test repository - * @return The parsed test.json file - * @throws StructuralSolutionEntryGenerationException If the test.json does not exist or could not be read - */ - private StructuralClassElements[] readStructureOracleFile(Path testRepoPath) throws StructuralSolutionEntryGenerationException { - try (Stream files = Files.walk(testRepoPath)) { - var testJsonFile = files.filter(Files::isRegularFile).filter(path -> "test.json".equals(path.getFileName().toString())).findFirst(); - if (testJsonFile.isEmpty()) { - throw new StructuralSolutionEntryGenerationException("Unable to locate test.json"); - } - else { - String jsonContent = Files.readString(testJsonFile.get()); - var objectMapper = new ObjectMapper(); - return objectMapper.readValue(jsonContent, StructuralClassElements[].class); - } - } - catch (IOException e) { - throw new StructuralSolutionEntryGenerationException("Error while reading test.json", e); - } - } - - /** - * Collects all java source files in a given path. - * - * @param start The base path - * @return The paths to all java source files - * @throws StructuralSolutionEntryGenerationException If there was an IOException - */ - private List retrieveJavaSourceFiles(Path start) throws StructuralSolutionEntryGenerationException { - var matcher = FileSystems.getDefault().getPathMatcher("glob:**/*.java"); - try (Stream files = Files.walk(start)) { - return files.filter(Files::isRegularFile).filter(matcher::matches).toList(); - } - catch (IOException e) { - var error = "Could not retrieve the project files to generate the structural solution entries"; - log.error(error, e); - throw new StructuralSolutionEntryGenerationException(error, e); - } - } - - private Map getClassesFromFiles(List javaSourceFiles) throws StructuralSolutionEntryGenerationException { - JavaProjectBuilder builder = new JavaProjectBuilder(); - try { - for (Path source : javaSourceFiles) { - builder.addSource(source.toFile()); - } - } - catch (IOException e) { - var error = "Could not add java source to builder"; - log.error(error, e); - throw new StructuralSolutionEntryGenerationException(error, e); - } - return builder.getClasses().stream().collect(Collectors.toMap(JavaType::getFullyQualifiedName, clazz -> clazz)); - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsProgrammingLanguageFeatureService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsProgrammingLanguageFeatureService.java index cca2995cdba0..a858a171efd7 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsProgrammingLanguageFeatureService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsProgrammingLanguageFeatureService.java @@ -37,21 +37,21 @@ public class JenkinsProgrammingLanguageFeatureService extends ProgrammingLanguag public JenkinsProgrammingLanguageFeatureService() { // Must be extended once a new programming language is added - programmingLanguageFeatures.put(EMPTY, new ProgrammingLanguageFeature(EMPTY, false, false, false, false, false, List.of(), false, false)); - programmingLanguageFeatures.put(C, new ProgrammingLanguageFeature(C, false, false, true, false, false, List.of(FACT, GCC), false, false)); - programmingLanguageFeatures.put(C_PLUS_PLUS, new ProgrammingLanguageFeature(C_PLUS_PLUS, false, false, true, false, false, List.of(), false, false)); - programmingLanguageFeatures.put(C_SHARP, new ProgrammingLanguageFeature(C_SHARP, false, false, true, false, false, List.of(), false, false)); - programmingLanguageFeatures.put(GO, new ProgrammingLanguageFeature(GO, false, false, true, true, false, List.of(), false, false)); - programmingLanguageFeatures.put(HASKELL, new ProgrammingLanguageFeature(HASKELL, false, false, false, false, true, List.of(), false, false)); + programmingLanguageFeatures.put(EMPTY, new ProgrammingLanguageFeature(EMPTY, false, false, false, false, false, List.of(), false)); + programmingLanguageFeatures.put(C, new ProgrammingLanguageFeature(C, false, false, true, false, false, List.of(FACT, GCC), false)); + programmingLanguageFeatures.put(C_PLUS_PLUS, new ProgrammingLanguageFeature(C_PLUS_PLUS, false, false, true, false, false, List.of(), false)); + programmingLanguageFeatures.put(C_SHARP, new ProgrammingLanguageFeature(C_SHARP, false, false, true, false, false, List.of(), false)); + programmingLanguageFeatures.put(GO, new ProgrammingLanguageFeature(GO, false, false, true, true, false, List.of(), false)); + programmingLanguageFeatures.put(HASKELL, new ProgrammingLanguageFeature(HASKELL, false, false, false, false, true, List.of(), false)); programmingLanguageFeatures.put(JAVA, - new ProgrammingLanguageFeature(JAVA, true, true, true, true, false, List.of(PLAIN_GRADLE, GRADLE_GRADLE, PLAIN_MAVEN, MAVEN_MAVEN, MAVEN_BLACKBOX), true, false)); - programmingLanguageFeatures.put(JAVASCRIPT, new ProgrammingLanguageFeature(JAVASCRIPT, false, false, true, false, false, List.of(), false, false)); - programmingLanguageFeatures.put(KOTLIN, new ProgrammingLanguageFeature(KOTLIN, true, false, true, true, false, List.of(), true, false)); - programmingLanguageFeatures.put(PYTHON, new ProgrammingLanguageFeature(PYTHON, false, false, true, false, false, List.of(), false, false)); - programmingLanguageFeatures.put(R, new ProgrammingLanguageFeature(R, false, false, true, false, false, List.of(), false, false)); - programmingLanguageFeatures.put(RUST, new ProgrammingLanguageFeature(RUST, false, false, true, false, false, List.of(), false, false)); + new ProgrammingLanguageFeature(JAVA, true, true, true, true, false, List.of(PLAIN_GRADLE, GRADLE_GRADLE, PLAIN_MAVEN, MAVEN_MAVEN, MAVEN_BLACKBOX), false)); + programmingLanguageFeatures.put(JAVASCRIPT, new ProgrammingLanguageFeature(JAVASCRIPT, false, false, true, false, false, List.of(), false)); + programmingLanguageFeatures.put(KOTLIN, new ProgrammingLanguageFeature(KOTLIN, true, false, true, true, false, List.of(), false)); + programmingLanguageFeatures.put(PYTHON, new ProgrammingLanguageFeature(PYTHON, false, false, true, false, false, List.of(), false)); + programmingLanguageFeatures.put(R, new ProgrammingLanguageFeature(R, false, false, true, false, false, List.of(), false)); + programmingLanguageFeatures.put(RUST, new ProgrammingLanguageFeature(RUST, false, false, true, false, false, List.of(), false)); // Jenkins is not supporting XCODE at the moment - programmingLanguageFeatures.put(SWIFT, new ProgrammingLanguageFeature(SWIFT, false, true, true, true, false, List.of(PLAIN), false, false)); - programmingLanguageFeatures.put(TYPESCRIPT, new ProgrammingLanguageFeature(TYPESCRIPT, false, false, true, false, false, List.of(), false, false)); + programmingLanguageFeatures.put(SWIFT, new ProgrammingLanguageFeature(SWIFT, false, true, true, true, false, List.of(PLAIN), false)); + programmingLanguageFeatures.put(TYPESCRIPT, new ProgrammingLanguageFeature(TYPESCRIPT, false, false, true, false, false, List.of(), false)); } } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsResultService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsResultService.java index 9c27aa4c5733..2730c9e82f9a 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsResultService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsResultService.java @@ -10,21 +10,17 @@ import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; -import de.tum.cit.aet.artemis.assessment.repository.FeedbackRepository; import de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage; import de.tum.cit.aet.artemis.programming.domain.ProgrammingSubmission; import de.tum.cit.aet.artemis.programming.domain.ProjectType; import de.tum.cit.aet.artemis.programming.domain.build.BuildLogEntry; -import de.tum.cit.aet.artemis.programming.dto.AbstractBuildResultNotificationDTO; +import de.tum.cit.aet.artemis.programming.dto.BuildResultNotification; import de.tum.cit.aet.artemis.programming.repository.BuildLogStatisticsEntryRepository; import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseBuildConfigRepository; import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseTestCaseRepository; -import de.tum.cit.aet.artemis.programming.repository.ProgrammingSubmissionRepository; -import de.tum.cit.aet.artemis.programming.service.BuildLogEntryService; import de.tum.cit.aet.artemis.programming.service.ProgrammingExerciseFeedbackCreationService; import de.tum.cit.aet.artemis.programming.service.ci.AbstractContinuousIntegrationResultService; import de.tum.cit.aet.artemis.programming.service.ci.notification.dto.TestResultsDTO; -import de.tum.cit.aet.artemis.programming.service.hestia.TestwiseCoverageService; @Profile("jenkins") @Service @@ -32,15 +28,13 @@ public class JenkinsResultService extends AbstractContinuousIntegrationResultSer private static final Logger log = LoggerFactory.getLogger(JenkinsResultService.class); - public JenkinsResultService(ProgrammingSubmissionRepository programmingSubmissionRepository, FeedbackRepository feedbackRepository, BuildLogEntryService buildLogService, - BuildLogStatisticsEntryRepository buildLogStatisticsEntryRepository, TestwiseCoverageService testwiseCoverageService, - ProgrammingExerciseFeedbackCreationService feedbackCreationService, ProgrammingExerciseTestCaseRepository testCaseRepository, - ProgrammingExerciseBuildConfigRepository programmingExerciseBuildConfigRepository) { - super(testCaseRepository, buildLogStatisticsEntryRepository, testwiseCoverageService, feedbackCreationService, programmingExerciseBuildConfigRepository); + public JenkinsResultService(BuildLogStatisticsEntryRepository buildLogStatisticsEntryRepository, ProgrammingExerciseFeedbackCreationService feedbackCreationService, + ProgrammingExerciseTestCaseRepository testCaseRepository, ProgrammingExerciseBuildConfigRepository programmingExerciseBuildConfigRepository) { + super(testCaseRepository, buildLogStatisticsEntryRepository, feedbackCreationService, programmingExerciseBuildConfigRepository); } @Override - public AbstractBuildResultNotificationDTO convertBuildResult(Object requestBody) { + public BuildResultNotification convertBuildResult(Object requestBody) { return TestResultsDTO.convert(requestBody); } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsService.java index 4a4e42f602a4..887bef4857b9 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsService.java @@ -30,9 +30,9 @@ import de.tum.cit.aet.artemis.programming.domain.VcsRepositoryUri; import de.tum.cit.aet.artemis.programming.domain.build.BuildPlanType; import de.tum.cit.aet.artemis.programming.dto.CheckoutDirectoriesDTO; +import de.tum.cit.aet.artemis.programming.dto.aeolus.Windfile; import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseBuildConfigRepository; import de.tum.cit.aet.artemis.programming.service.aeolus.AeolusTemplateService; -import de.tum.cit.aet.artemis.programming.service.aeolus.Windfile; import de.tum.cit.aet.artemis.programming.service.ci.AbstractContinuousIntegrationService; import de.tum.cit.aet.artemis.programming.service.ci.CIPermission; import de.tum.cit.aet.artemis.programming.service.ci.notification.dto.TestResultsDTO; diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/build_plan/JenkinsBuildPlanService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/build_plan/JenkinsBuildPlanService.java index 1264ba9b643f..c54b617d5546 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/build_plan/JenkinsBuildPlanService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/build_plan/JenkinsBuildPlanService.java @@ -48,12 +48,13 @@ import de.tum.cit.aet.artemis.programming.domain.ProjectType; import de.tum.cit.aet.artemis.programming.domain.RepositoryType; import de.tum.cit.aet.artemis.programming.domain.VcsRepositoryUri; +import de.tum.cit.aet.artemis.programming.dto.aeolus.AeolusRepository; +import de.tum.cit.aet.artemis.programming.dto.aeolus.Windfile; +import de.tum.cit.aet.artemis.programming.dto.aeolus.WindfileMetadata; import de.tum.cit.aet.artemis.programming.repository.BuildPlanRepository; import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseBuildConfigRepository; import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseRepository; import de.tum.cit.aet.artemis.programming.service.aeolus.AeolusBuildPlanService; -import de.tum.cit.aet.artemis.programming.service.aeolus.AeolusRepository; -import de.tum.cit.aet.artemis.programming.service.aeolus.Windfile; import de.tum.cit.aet.artemis.programming.service.ci.ContinuousIntegrationService; import de.tum.cit.aet.artemis.programming.service.ci.notification.dto.TestResultsDTO; import de.tum.cit.aet.artemis.programming.service.jenkins.JenkinsEndpoints; @@ -287,7 +288,7 @@ private void postBuildPlanConfigChange(String buildPlanKey, String buildProjectK * @return the build plan key */ public String getBuildPlanKeyFromTestResults(TestResultsDTO testResultsDTO) throws JsonProcessingException { - final var nameParams = testResultsDTO.getFullName().split(" "); + final var nameParams = testResultsDTO.fullName().split(" "); /* * Jenkins gives the full name of a job as » E.g. the third build of an exercise (projectKey = TESTEXC) for its solution build * (TESTEXC-SOLUTION) would be: TESTEXC » TESTEXC-SOLUTION #3 ==> This would mean that at index 2, we have the actual job/plan key, i.e. TESTEXC-SOLUTION @@ -507,8 +508,9 @@ private String createCustomAeolusBuildPlanForExercise(ProgrammingExercise progra buildConfig.getBranch(), buildConfig.getCheckoutSolutionRepository(), repositoryUri, testRepositoryUri, solutionRepositoryUri, List.of()); String resultHookUrl = artemisServerUrl + NEW_RESULT_RESOURCE_API_PATH; - windfile.setPreProcessingMetadata(buildPlanId, programmingExercise.getProjectName(), this.vcsCredentials, resultHookUrl, "planDescription", repositories, - this.artemisAuthenticationTokenKey); + var metadata = new WindfileMetadata(programmingExercise.getProjectName(), buildPlanId, "planDescription", null, vcsCredentials, null, resultHookUrl, + artemisAuthenticationTokenKey); + windfile = new Windfile(windfile, metadata, repositories); String generatedKey = aeolusBuildPlanService.get().publishBuildPlan(windfile, AeolusTarget.JENKINS); if (generatedKey != null && generatedKey.contains("-")) { diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/build_plan/JenkinsPipelineScriptCreator.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/build_plan/JenkinsPipelineScriptCreator.java index 92fca529c68b..a18f3ea8ffc5 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/build_plan/JenkinsPipelineScriptCreator.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/build_plan/JenkinsPipelineScriptCreator.java @@ -36,8 +36,6 @@ public class JenkinsPipelineScriptCreator extends AbstractBuildPlanCreator { private static final String REPLACE_IS_STATIC_CODE_ANALYSIS_ENABLED = "#isStaticCodeAnalysisEnabled"; - private static final String REPLACE_TESTWISE_COVERAGE = "#testWiseCoverage"; - private final ResourceLoaderService resourceLoaderService; private final ProgrammingLanguageConfiguration programmingLanguageConfiguration; @@ -58,8 +56,7 @@ protected String generateDefaultBuildPlan(final ProgrammingExercise exercise) { final String pipelineScript = loadPipelineScript(exercise, projectType); final boolean isStaticCodeAnalysisEnabled = exercise.isStaticCodeAnalysisEnabled(); - final boolean isTestwiseCoverageAnalysisEnabled = exercise.getBuildConfig().isTestwiseCoverageEnabled(); - final var replacements = getReplacements(programmingLanguage, projectType, isStaticCodeAnalysisEnabled, isTestwiseCoverageAnalysisEnabled); + final var replacements = getReplacements(programmingLanguage, projectType, isStaticCodeAnalysisEnabled); return replaceVariablesInBuildPlanTemplate(replacements, pipelineScript); } @@ -86,12 +83,10 @@ private String loadPipelineScript(final ProgrammingExercise exercise, final Opti } } - private Map getReplacements(final ProgrammingLanguage programmingLanguage, final Optional projectType, final boolean isStaticCodeAnalysisEnabled, - final boolean isTestwiseCoverageAnalysisEnabled) { + private Map getReplacements(final ProgrammingLanguage programmingLanguage, final Optional projectType, final boolean isStaticCodeAnalysisEnabled) { final Map replacements = new HashMap<>(); replacements.put(REPLACE_IS_STATIC_CODE_ANALYSIS_ENABLED, String.valueOf(isStaticCodeAnalysisEnabled)); - replacements.put(REPLACE_TESTWISE_COVERAGE, String.valueOf(isTestwiseCoverageAnalysisEnabled)); replacements.put(REPLACE_DOCKER_IMAGE_NAME, programmingLanguageConfiguration.getImage(programmingLanguage, projectType)); replacements.put(REPLACE_DOCKER_ARGS, String.join(" ", programmingLanguageConfiguration.getDefaultDockerFlags())); diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/LocalCIBuildConfigurationService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/LocalCIBuildConfigurationService.java index e4803619c97d..498f416f61ed 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/LocalCIBuildConfigurationService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/LocalCIBuildConfigurationService.java @@ -11,10 +11,10 @@ import de.tum.cit.aet.artemis.core.exception.LocalCIException; import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise; import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseBuildConfig; +import de.tum.cit.aet.artemis.programming.dto.aeolus.ScriptAction; +import de.tum.cit.aet.artemis.programming.dto.aeolus.Windfile; import de.tum.cit.aet.artemis.programming.service.BuildScriptProviderService; import de.tum.cit.aet.artemis.programming.service.aeolus.AeolusTemplateService; -import de.tum.cit.aet.artemis.programming.service.aeolus.ScriptAction; -import de.tum.cit.aet.artemis.programming.service.aeolus.Windfile; @Service @Profile(PROFILE_LOCALCI) @@ -57,18 +57,18 @@ public String createBuildScript(ProgrammingExercise programmingExercise) { windfile = aeolusTemplateService.getDefaultWindfileFor(programmingExercise); } if (windfile != null) { - actions = windfile.getScriptActions(); + actions = windfile.scriptActions(); } else { throw new LocalCIException("No windfile found for programming exercise " + programmingExercise.getId()); } actions.forEach(action -> { - String workdir = action.getWorkdir(); + String workdir = action.workdir(); if (workdir != null) { buildScriptBuilder.append("cd ").append(LOCALCI_WORKING_DIRECTORY).append("/testing-dir/").append(workdir).append("\n"); } - buildScriptBuilder.append(action.getScript()).append("\n"); + buildScriptBuilder.append(action.script()).append("\n"); if (workdir != null) { buildScriptBuilder.append("cd ").append(LOCALCI_WORKING_DIRECTORY).append("/testing-dir\n"); } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/LocalCIProgrammingLanguageFeatureService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/LocalCIProgrammingLanguageFeatureService.java index d764193795c9..0c46a202574b 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/LocalCIProgrammingLanguageFeatureService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/LocalCIProgrammingLanguageFeatureService.java @@ -43,23 +43,23 @@ public class LocalCIProgrammingLanguageFeatureService extends ProgrammingLanguag public LocalCIProgrammingLanguageFeatureService() { // Must be extended once a new programming language is added - programmingLanguageFeatures.put(EMPTY, new ProgrammingLanguageFeature(EMPTY, false, false, false, false, false, List.of(), false, true)); - programmingLanguageFeatures.put(ASSEMBLER, new ProgrammingLanguageFeature(ASSEMBLER, false, false, false, false, false, List.of(), false, true)); - programmingLanguageFeatures.put(C, new ProgrammingLanguageFeature(C, false, true, true, false, false, List.of(FACT, GCC), false, true)); - programmingLanguageFeatures.put(C_PLUS_PLUS, new ProgrammingLanguageFeature(C_PLUS_PLUS, false, false, true, false, false, List.of(), false, true)); - programmingLanguageFeatures.put(C_SHARP, new ProgrammingLanguageFeature(C_SHARP, false, false, true, false, false, List.of(), false, true)); - programmingLanguageFeatures.put(GO, new ProgrammingLanguageFeature(GO, false, false, true, true, false, List.of(), false, true)); - programmingLanguageFeatures.put(HASKELL, new ProgrammingLanguageFeature(HASKELL, true, false, false, false, true, List.of(), false, true)); + programmingLanguageFeatures.put(EMPTY, new ProgrammingLanguageFeature(EMPTY, false, false, false, false, false, List.of(), true)); + programmingLanguageFeatures.put(ASSEMBLER, new ProgrammingLanguageFeature(ASSEMBLER, false, false, false, false, false, List.of(), true)); + programmingLanguageFeatures.put(C, new ProgrammingLanguageFeature(C, false, true, true, false, false, List.of(FACT, GCC), true)); + programmingLanguageFeatures.put(C_PLUS_PLUS, new ProgrammingLanguageFeature(C_PLUS_PLUS, false, false, true, false, false, List.of(), true)); + programmingLanguageFeatures.put(C_SHARP, new ProgrammingLanguageFeature(C_SHARP, false, false, true, false, false, List.of(), true)); + programmingLanguageFeatures.put(GO, new ProgrammingLanguageFeature(GO, false, false, true, true, false, List.of(), true)); + programmingLanguageFeatures.put(HASKELL, new ProgrammingLanguageFeature(HASKELL, true, false, false, false, true, List.of(), true)); programmingLanguageFeatures.put(JAVA, - new ProgrammingLanguageFeature(JAVA, true, true, true, true, false, List.of(PLAIN_GRADLE, GRADLE_GRADLE, PLAIN_MAVEN, MAVEN_MAVEN), false, true)); - programmingLanguageFeatures.put(JAVASCRIPT, new ProgrammingLanguageFeature(JAVASCRIPT, false, false, true, false, false, List.of(), false, true)); - programmingLanguageFeatures.put(KOTLIN, new ProgrammingLanguageFeature(KOTLIN, false, false, true, true, false, List.of(), false, true)); - programmingLanguageFeatures.put(OCAML, new ProgrammingLanguageFeature(OCAML, false, false, false, false, true, List.of(), false, true)); - programmingLanguageFeatures.put(PYTHON, new ProgrammingLanguageFeature(PYTHON, false, true, true, false, false, List.of(), false, true)); - programmingLanguageFeatures.put(R, new ProgrammingLanguageFeature(R, false, false, true, false, false, List.of(), false, true)); - programmingLanguageFeatures.put(RUST, new ProgrammingLanguageFeature(RUST, false, false, true, false, false, List.of(), false, true)); - programmingLanguageFeatures.put(SWIFT, new ProgrammingLanguageFeature(SWIFT, false, false, true, true, false, List.of(PLAIN), false, true)); - programmingLanguageFeatures.put(TYPESCRIPT, new ProgrammingLanguageFeature(TYPESCRIPT, false, false, true, false, false, List.of(), false, true)); - programmingLanguageFeatures.put(VHDL, new ProgrammingLanguageFeature(VHDL, false, false, false, false, false, List.of(), false, true)); + new ProgrammingLanguageFeature(JAVA, true, true, true, true, false, List.of(PLAIN_GRADLE, GRADLE_GRADLE, PLAIN_MAVEN, MAVEN_MAVEN), true)); + programmingLanguageFeatures.put(JAVASCRIPT, new ProgrammingLanguageFeature(JAVASCRIPT, false, false, true, false, false, List.of(), true)); + programmingLanguageFeatures.put(KOTLIN, new ProgrammingLanguageFeature(KOTLIN, false, false, true, true, false, List.of(), true)); + programmingLanguageFeatures.put(OCAML, new ProgrammingLanguageFeature(OCAML, false, false, false, false, true, List.of(), true)); + programmingLanguageFeatures.put(PYTHON, new ProgrammingLanguageFeature(PYTHON, false, true, true, false, false, List.of(), true)); + programmingLanguageFeatures.put(R, new ProgrammingLanguageFeature(R, false, false, true, false, false, List.of(), true)); + programmingLanguageFeatures.put(RUST, new ProgrammingLanguageFeature(RUST, false, false, true, false, false, List.of(), true)); + programmingLanguageFeatures.put(SWIFT, new ProgrammingLanguageFeature(SWIFT, false, false, true, true, false, List.of(PLAIN), true)); + programmingLanguageFeatures.put(TYPESCRIPT, new ProgrammingLanguageFeature(TYPESCRIPT, false, false, true, false, false, List.of(), true)); + programmingLanguageFeatures.put(VHDL, new ProgrammingLanguageFeature(VHDL, false, false, false, false, false, List.of(), true)); } } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/LocalCIQueueWebsocketService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/LocalCIQueueWebsocketService.java index 872057e95145..f82777abddb2 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/LocalCIQueueWebsocketService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/LocalCIQueueWebsocketService.java @@ -182,8 +182,8 @@ private static List removeUnnecessaryInformation(List getTestResultPaths(Windfile windfile) throws IllegalArgumentException { - List testResultPaths = new ArrayList<>(); - for (AeolusResult testResultPath : windfile.getResults()) { - testResultPaths.add(LOCALCI_WORKING_DIRECTORY + "/testing-dir/" + testResultPath.path()); - } - return testResultPaths; + private List getTestResultPaths(Windfile windfile) { + return windfile.results().stream().map(result -> LOCALCI_WORKING_DIRECTORY + "/testing-dir/" + result.path()).toList(); } /** @@ -319,13 +313,12 @@ private BuildConfig getBuildConfig(ProgrammingExerciseParticipation participatio ProjectType projectType = programmingExercise.getProjectType(); boolean staticCodeAnalysisEnabled = programmingExercise.isStaticCodeAnalysisEnabled(); boolean sequentialTestRunsEnabled = buildConfig.hasSequentialTestRuns(); - boolean testwiseCoverageEnabled = buildConfig.isTestwiseCoverageEnabled(); Windfile windfile; String dockerImage; try { windfile = buildConfig.getWindfile(); - dockerImage = windfile.getMetadata().docker().getFullImageName(); + dockerImage = windfile.metadata().docker().getFullImageName(); } catch (NullPointerException e) { log.warn("Could not retrieve windfile for programming exercise {}. Using default windfile instead.", programmingExercise.getId()); @@ -343,8 +336,8 @@ private BuildConfig getBuildConfig(ProgrammingExerciseParticipation participatio String buildScript = localCIBuildConfigurationService.createBuildScript(programmingExercise); return new BuildConfig(buildScript, dockerImage, commitHashToBuild, assignmentCommitHash, testCommitHash, branch, programmingLanguage, projectType, - staticCodeAnalysisEnabled, sequentialTestRunsEnabled, testwiseCoverageEnabled, resultPaths, buildConfig.getTimeoutSeconds(), - buildConfig.getAssignmentCheckoutPath(), buildConfig.getTestCheckoutPath(), buildConfig.getSolutionCheckoutPath(), dockerRunConfig); + staticCodeAnalysisEnabled, sequentialTestRunsEnabled, resultPaths, buildConfig.getTimeoutSeconds(), buildConfig.getAssignmentCheckoutPath(), + buildConfig.getTestCheckoutPath(), buildConfig.getSolutionCheckoutPath(), dockerRunConfig); } private ProgrammingExerciseBuildConfig loadBuildConfig(ProgrammingExercise programmingExercise) { diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/web/ProgrammingExerciseExportImportResource.java b/src/main/java/de/tum/cit/aet/artemis/programming/web/ProgrammingExerciseExportImportResource.java index da74833808c0..7079d2e2994e 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/web/ProgrammingExerciseExportImportResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/web/ProgrammingExerciseExportImportResource.java @@ -73,7 +73,7 @@ import de.tum.cit.aet.artemis.programming.domain.RepositoryType; import de.tum.cit.aet.artemis.programming.repository.AuxiliaryRepositoryRepository; import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseRepository; -import de.tum.cit.aet.artemis.programming.repository.hestia.ProgrammingExerciseTaskRepository; +import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseTaskRepository; import de.tum.cit.aet.artemis.programming.service.ConsistencyCheckService; import de.tum.cit.aet.artemis.programming.service.ProgrammingExerciseExportService; import de.tum.cit.aet.artemis.programming.service.ProgrammingExerciseImportFromFileService; @@ -214,8 +214,7 @@ public ResponseEntity importProgrammingExercise(@PathVariab programmingExerciseRepository.validateCourseSettings(newExercise, course); final var originalProgrammingExercise = programmingExerciseRepository - .findByIdWithEagerBuildConfigTestCasesStaticCodeAnalysisCategoriesHintsAndTemplateAndSolutionParticipationsAndAuxReposAndSolutionEntriesAndBuildConfig( - sourceExerciseId) + .findByIdWithEagerBuildConfigTestCasesStaticCodeAnalysisCategoriesAndTemplateAndSolutionParticipationsAndAuxReposAndAndBuildConfig(sourceExerciseId) .orElseThrow(() -> new EntityNotFoundException("ProgrammingExercise", sourceExerciseId)); var consistencyErrors = consistencyCheckService.checkConsistencyOfProgrammingExercise(originalProgrammingExercise); @@ -261,7 +260,6 @@ public ResponseEntity importProgrammingExercise(@PathVariab importedProgrammingExercise.setStaticCodeAnalysisCategories(null); importedProgrammingExercise.setTemplateParticipation(null); importedProgrammingExercise.setSolutionParticipation(null); - importedProgrammingExercise.setExerciseHints(null); importedProgrammingExercise.setTasks(null); competencyProgressApi.updateProgressByLearningObjectAsync(importedProgrammingExercise); diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/web/hestia/ProgrammingExerciseGitDiffReportResource.java b/src/main/java/de/tum/cit/aet/artemis/programming/web/ProgrammingExerciseGitDiffReportResource.java similarity index 98% rename from src/main/java/de/tum/cit/aet/artemis/programming/web/hestia/ProgrammingExerciseGitDiffReportResource.java rename to src/main/java/de/tum/cit/aet/artemis/programming/web/ProgrammingExerciseGitDiffReportResource.java index 5dad0330b670..b561b6b331bb 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/web/hestia/ProgrammingExerciseGitDiffReportResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/web/ProgrammingExerciseGitDiffReportResource.java @@ -1,4 +1,4 @@ -package de.tum.cit.aet.artemis.programming.web.hestia; +package de.tum.cit.aet.artemis.programming.web; import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; @@ -25,15 +25,15 @@ import de.tum.cit.aet.artemis.exercise.repository.ParticipationRepository; import de.tum.cit.aet.artemis.exercise.service.ParticipationAuthorizationCheckService; import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise; +import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseGitDiffReport; import de.tum.cit.aet.artemis.programming.domain.RepositoryType; import de.tum.cit.aet.artemis.programming.domain.VcsRepositoryUri; -import de.tum.cit.aet.artemis.programming.domain.hestia.ProgrammingExerciseGitDiffReport; import de.tum.cit.aet.artemis.programming.dto.ProgrammingExerciseGitDiffReportDTO; import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseRepository; import de.tum.cit.aet.artemis.programming.repository.ProgrammingSubmissionRepository; import de.tum.cit.aet.artemis.programming.service.CommitHistoryService; +import de.tum.cit.aet.artemis.programming.service.ProgrammingExerciseGitDiffReportService; import de.tum.cit.aet.artemis.programming.service.RepositoryService; -import de.tum.cit.aet.artemis.programming.service.hestia.ProgrammingExerciseGitDiffReportService; /** * REST controller for managing ProgrammingExerciseGitDiffReports and its entries. diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/web/ProgrammingExercisePlagiarismResource.java b/src/main/java/de/tum/cit/aet/artemis/programming/web/ProgrammingExercisePlagiarismResource.java index 95e60a0ec89c..1537d7e7cbe7 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/web/ProgrammingExercisePlagiarismResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/web/ProgrammingExercisePlagiarismResource.java @@ -105,7 +105,7 @@ public ResponseEntity> checkPlagiarism log.info("Started manual plagiarism checks for programming exercise: exerciseId={}.", exerciseId); PlagiarismDetectionConfigHelper.updateWithTemporaryParameters(programmingExercise, similarityThreshold, minimumScore, minimumSize); try { - var plagiarismResult = (TextPlagiarismResult) plagiarismDetectionService.checkProgrammingExercise(programmingExercise); + var plagiarismResult = plagiarismDetectionService.checkProgrammingExercise(programmingExercise); return buildPlagiarismResultResponse(plagiarismResult); } catch (ProgrammingLanguageNotSupportedForPlagiarismDetectionException e) { diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/web/ProgrammingExerciseResource.java b/src/main/java/de/tum/cit/aet/artemis/programming/web/ProgrammingExerciseResource.java index 0eaab82ce448..9d472c2cfbf1 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/web/ProgrammingExerciseResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/web/ProgrammingExerciseResource.java @@ -86,10 +86,10 @@ import de.tum.cit.aet.artemis.programming.service.GitService; import de.tum.cit.aet.artemis.programming.service.ProgrammingExerciseRepositoryService; import de.tum.cit.aet.artemis.programming.service.ProgrammingExerciseService; +import de.tum.cit.aet.artemis.programming.service.ProgrammingExerciseTaskService; import de.tum.cit.aet.artemis.programming.service.ProgrammingExerciseTestCaseService; import de.tum.cit.aet.artemis.programming.service.StaticCodeAnalysisService; import de.tum.cit.aet.artemis.programming.service.ci.ContinuousIntegrationService; -import de.tum.cit.aet.artemis.programming.service.hestia.ProgrammingExerciseTaskService; import de.tum.cit.aet.artemis.programming.service.vcs.VersionControlService; import io.jsonwebtoken.lang.Arrays; @@ -305,10 +305,6 @@ public ResponseEntity updateProgrammingExercise(@RequestBod if (!Objects.equals(programmingExerciseBeforeUpdate.isStaticCodeAnalysisEnabled(), updatedProgrammingExercise.isStaticCodeAnalysisEnabled())) { throw new BadRequestAlertException("Static code analysis enabled flag must not be changed", ENTITY_NAME, "staticCodeAnalysisCannotChange"); } - if (!Objects.equals(programmingExerciseBeforeUpdate.getBuildConfig().isTestwiseCoverageEnabled(), - updatedProgrammingExercise.getBuildConfig().isTestwiseCoverageEnabled())) { - throw new BadRequestAlertException("Testwise coverage enabled flag must not be changed", ENTITY_NAME, "testwiseCoverageCannotChange"); - } // Check if theia Profile is enabled if (Arrays.asList(this.environment.getActiveProfiles()).contains(PROFILE_THEIA)) { // Require 1 / 3 participation modes to be enabled @@ -791,9 +787,7 @@ public ResponseEntity reEvaluateAndUpdateProgrammingExercis } /** - * DELETE programming-exercises/:exerciseId/tasks : Delete all tasks and solution entries for an existing ProgrammingExercise. - * Note: This endpoint exists only for testing purposes and will be removed at a later stage of the development of HESTIA - * (automatic generation of code hints for programming exercises in Java). + * DELETE programming-exercises/:exerciseId/tasks : Delete all tasks for an existing ProgrammingExercise. * * @param exerciseId of the exercise * @return the {@link ResponseEntity} with status {@code 204}, @@ -802,12 +796,12 @@ public ResponseEntity reEvaluateAndUpdateProgrammingExercis @DeleteMapping("programming-exercises/{exerciseId}/tasks") @EnforceAtLeastEditor @FeatureToggle(Feature.ProgrammingExercises) - public ResponseEntity deleteTaskWithSolutionEntries(@PathVariable Long exerciseId) { - log.debug("REST request to delete ProgrammingExerciseTasks with ProgrammingExerciseSolutionEntries for ProgrammingExercise with id : {}", exerciseId); + public ResponseEntity deleteTasks(@PathVariable Long exerciseId) { + log.debug("REST request to delete tasks for ProgrammingExercise with id : {}", exerciseId); ProgrammingExercise exercise = programmingExerciseRepository.findByIdElseThrow(exerciseId); authCheckService.checkHasAtLeastRoleForExerciseElseThrow(Role.EDITOR, exercise, null); - programmingExerciseService.deleteTasksWithSolutionEntries(exercise.getId()); + programmingExerciseService.deleteTasks(exercise.getId()); return ResponseEntity.noContent().build(); } @@ -859,30 +853,6 @@ public ModelAndView redirectGetTemplateRepositoryFiles(@PathVariable Long exerci return new ModelAndView("forward:/api/repository/" + participation.getId() + "/files-content"); } - /** - * GET programming-exercises/:exerciseId/solution-file-names - *

- * Returns the solution repository file names for a given programming exercise. - * Note: This endpoint redirects the request to the ProgrammingExerciseParticipationService. This is required if - * the solution participation id is not known for the client. - * - * @param exerciseId the exercise for which the solution repository files should be retrieved - * @return a redirect to the endpoint returning the files with content - */ - @GetMapping("programming-exercises/{exerciseId}/file-names") - @EnforceAtLeastTutor - @FeatureToggle(Feature.ProgrammingExercises) - public ModelAndView redirectGetSolutionRepositoryFilesWithoutContent(@PathVariable Long exerciseId) { - log.debug("REST request to get latest solution repository file names for ProgrammingExercise with id : {}", exerciseId); - ProgrammingExercise exercise = programmingExerciseRepository.findByIdElseThrow(exerciseId); - authCheckService.checkHasAtLeastRoleForExerciseElseThrow(Role.TEACHING_ASSISTANT, exercise, null); - - var participation = solutionProgrammingExerciseParticipationRepository.findByProgrammingExerciseIdElseThrow(exerciseId); - - // TODO: We want to get rid of ModelAndView and use ResponseEntity instead. Define an appropriate service method and then call it here and in the referenced endpoint. - return new ModelAndView("forward:/api/repository/" + participation.getId() + "/file-names"); - } - /** * GET programming-exercises/:exerciseId/build-log-statistics *

diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/web/hestia/ProgrammingExerciseTaskResource.java b/src/main/java/de/tum/cit/aet/artemis/programming/web/ProgrammingExerciseTaskResource.java similarity index 94% rename from src/main/java/de/tum/cit/aet/artemis/programming/web/hestia/ProgrammingExerciseTaskResource.java rename to src/main/java/de/tum/cit/aet/artemis/programming/web/ProgrammingExerciseTaskResource.java index 379ebfacb035..64b0a34c2b16 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/web/hestia/ProgrammingExerciseTaskResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/web/ProgrammingExerciseTaskResource.java @@ -1,4 +1,4 @@ -package de.tum.cit.aet.artemis.programming.web.hestia; +package de.tum.cit.aet.artemis.programming.web; import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; @@ -18,9 +18,9 @@ import de.tum.cit.aet.artemis.core.security.annotations.EnforceAtLeastTutor; import de.tum.cit.aet.artemis.core.service.AuthorizationCheckService; import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise; -import de.tum.cit.aet.artemis.programming.domain.hestia.ProgrammingExerciseTask; +import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseTask; import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseRepository; -import de.tum.cit.aet.artemis.programming.service.hestia.ProgrammingExerciseTaskService; +import de.tum.cit.aet.artemis.programming.service.ProgrammingExerciseTaskService; /** * REST controller for managing {@link ProgrammingExerciseTask}. diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/web/hestia/CodeHintResource.java b/src/main/java/de/tum/cit/aet/artemis/programming/web/hestia/CodeHintResource.java deleted file mode 100644 index 122aa11b7a17..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/web/hestia/CodeHintResource.java +++ /dev/null @@ -1,124 +0,0 @@ -package de.tum.cit.aet.artemis.programming.web.hestia; - -import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; - -import java.util.List; -import java.util.Objects; -import java.util.Set; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.annotation.Profile; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -import de.tum.cit.aet.artemis.core.exception.AccessForbiddenException; -import de.tum.cit.aet.artemis.core.exception.ConflictException; -import de.tum.cit.aet.artemis.core.security.annotations.enforceRoleInExercise.EnforceAtLeastEditorInExercise; -import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise; -import de.tum.cit.aet.artemis.programming.domain.hestia.CodeHint; -import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseRepository; -import de.tum.cit.aet.artemis.programming.repository.hestia.CodeHintRepository; -import de.tum.cit.aet.artemis.programming.repository.hestia.ProgrammingExerciseSolutionEntryRepository; -import de.tum.cit.aet.artemis.programming.service.hestia.CodeHintService; - -/** - * REST controller for managing {@link CodeHint}. - */ -@Profile(PROFILE_CORE) -@RestController -@RequestMapping("api/") -public class CodeHintResource { - - private static final Logger log = LoggerFactory.getLogger(CodeHintResource.class); - - private final ProgrammingExerciseRepository programmingExerciseRepository; - - private final ProgrammingExerciseSolutionEntryRepository solutionEntryRepository; - - private final CodeHintRepository codeHintRepository; - - private final CodeHintService codeHintService; - - public CodeHintResource(ProgrammingExerciseRepository programmingExerciseRepository, ProgrammingExerciseSolutionEntryRepository solutionEntryRepository, - CodeHintRepository codeHintRepository, CodeHintService codeHintService) { - this.programmingExerciseRepository = programmingExerciseRepository; - this.solutionEntryRepository = solutionEntryRepository; - this.codeHintRepository = codeHintRepository; - this.codeHintService = codeHintService; - } - - /** - * GET programming-exercises/{exerciseId}/code-hints: Retrieve all code hints for a programming exercise. - * - * @param exerciseId of the exercise - * @return the {@link ResponseEntity} with status {@code 200 (Ok)} and with body the code hints for the exercise - */ - @GetMapping("programming-exercises/{exerciseId}/code-hints") - @EnforceAtLeastEditorInExercise - public ResponseEntity> getAllCodeHints(@PathVariable Long exerciseId) { - var result = codeHintRepository.findByExerciseId(exerciseId); - return ResponseEntity.ok(result); - } - - /** - * {@code POST programming-exercises/:exerciseId/code-hints} : Create a new exerciseHint for an exercise. - * - * @param exerciseId the exerciseId of the exercise of which to create the exerciseHint - * @param deleteOldCodeHints Whether old code hints should be deleted - * @return the {@link ResponseEntity} with status {@code 200 (Ok)} and with body the new code hints - */ - @PostMapping("programming-exercises/{exerciseId}/code-hints") - @EnforceAtLeastEditorInExercise - public ResponseEntity> generateCodeHintsForExercise(@PathVariable Long exerciseId, - @RequestParam(value = "deleteOldCodeHints", defaultValue = "true") boolean deleteOldCodeHints) { - log.debug("REST request to generate CodeHints for ProgrammingExercise: {}", exerciseId); - - ProgrammingExercise exercise = programmingExerciseRepository.findByIdElseThrow(exerciseId); - - // Hints for exam exercises are not supported at the moment - if (exercise.isExamExercise()) { - throw new AccessForbiddenException("Code hints for exams are currently not supported"); - } - - var codeHints = codeHintService.generateCodeHintsForExercise(exercise, deleteOldCodeHints); - return ResponseEntity.ok(codeHints); - } - - /** - * {@code DELETE programming-exercises/:exerciseId/code-hints/:codeHintId/solution-entries/:solutionEntryId} : - * Removes a solution entry from a code hint. - * - * @param exerciseId The id of the exercise of the code hint - * @param codeHintId The id of the code hint - * @param solutionEntryId The id of the solution entry - * @return 204 No Content - */ - @DeleteMapping("programming-exercises/{exerciseId}/code-hints/{codeHintId}/solution-entries/{solutionEntryId}") - @EnforceAtLeastEditorInExercise - public ResponseEntity removeSolutionEntryFromCodeHint(@PathVariable Long exerciseId, @PathVariable Long codeHintId, @PathVariable Long solutionEntryId) { - log.debug("REST request to remove SolutionEntry {} from CodeHint {} in ProgrammingExercise {}", solutionEntryId, codeHintId, exerciseId); - - var codeHint = codeHintRepository.findByIdWithSolutionEntriesElseThrow(codeHintId); - if (!Objects.equals(codeHint.getExercise().getId(), exerciseId)) { - throw new ConflictException("The code hint does not belong to the exercise", "CodeHint", "codeHintExerciseConflict"); - } - - var solutionEntry = codeHint.getSolutionEntries().stream().filter(solutionEntry1 -> solutionEntry1.getId().equals(solutionEntryId)).findFirst().orElse(null); - if (solutionEntry == null) { - throw new ConflictException("The solution entry does not belong to the code hint", "SolutionEntry", "solutionEntryCodeHintConflict"); - } - - solutionEntry.setCodeHint(null); - solutionEntryRepository.save(solutionEntry); - codeHint.getSolutionEntries().remove(solutionEntry); - - return ResponseEntity.noContent().build(); - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/web/hestia/CoverageReportResource.java b/src/main/java/de/tum/cit/aet/artemis/programming/web/hestia/CoverageReportResource.java deleted file mode 100644 index ad2c9a574268..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/web/hestia/CoverageReportResource.java +++ /dev/null @@ -1,68 +0,0 @@ -package de.tum.cit.aet.artemis.programming.web.hestia; - -import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.annotation.Profile; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import de.tum.cit.aet.artemis.core.exception.EntityNotFoundException; -import de.tum.cit.aet.artemis.core.security.annotations.enforceRoleInExercise.EnforceAtLeastTutorInExercise; -import de.tum.cit.aet.artemis.programming.domain.hestia.CoverageReport; -import de.tum.cit.aet.artemis.programming.service.hestia.TestwiseCoverageService; - -/** - * REST controller for managing ProgrammingExerciseTestwiseCoverageReports and its entries. - */ -@Profile(PROFILE_CORE) -@RestController -@RequestMapping("api/") -public class CoverageReportResource { - - private static final Logger log = LoggerFactory.getLogger(CoverageReportResource.class); - - private final TestwiseCoverageService testwiseCoverageService; - - public CoverageReportResource(TestwiseCoverageService testwiseCoverageService) { - this.testwiseCoverageService = testwiseCoverageService; - } - - /** - * {@code GET exercises/:exerciseId/full-testwise-coverage-report} : Get the latest coverage report for a solution submission - * of a programming exercise with all file reports and descendants. - * - * @param exerciseId the exerciseId of the exercise of which to retrieve the testwise coverage report for the latest solution submission - * @return the {@link ResponseEntity} with status {@code 200 (Ok)} and with body the coverage report - */ - @GetMapping("programming-exercises/{exerciseId}/full-testwise-coverage-report") - @EnforceAtLeastTutorInExercise - public ResponseEntity getLatestFullCoverageReport(@PathVariable Long exerciseId) { - log.debug("REST request to get the latest Full Testwise CoverageReport for exercise {}", exerciseId); - - var optionalReportWithFileReports = testwiseCoverageService.getFullCoverageReportForLatestSolutionSubmissionFromProgrammingExercise(exerciseId) - .orElseThrow(() -> new EntityNotFoundException("Coverage report for exercise " + exerciseId + " not found.")); - return ResponseEntity.ok(optionalReportWithFileReports); - } - - /** - * {@code GET exercises/:exerciseId/testwise-coverage-report} : Get the latest coverage report for a solution submission - * of a programming exercise without the actual file reports. - * - * @param exerciseId the exerciseId of the exercise of which to retrieve the testwise coverage report for the latest solution submission - * @return the {@link ResponseEntity} with status {@code 200 (Ok)} and with body the coverage report - */ - @GetMapping("programming-exercises/{exerciseId}/testwise-coverage-report") - @EnforceAtLeastTutorInExercise - public ResponseEntity getLatestCoverageReport(@PathVariable Long exerciseId) { - log.debug("REST request to get the latest Testwise CoverageReport for exercise {}", exerciseId); - - var optionalReportWithoutFileReports = testwiseCoverageService.getCoverageReportForLatestSolutionSubmissionFromProgrammingExercise(exerciseId) - .orElseThrow(() -> new EntityNotFoundException("Coverage report for exercise " + exerciseId + " not found.")); - return ResponseEntity.ok(optionalReportWithoutFileReports); - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/web/hestia/ExerciseHintResource.java b/src/main/java/de/tum/cit/aet/artemis/programming/web/hestia/ExerciseHintResource.java deleted file mode 100644 index 029a374a2a87..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/web/hestia/ExerciseHintResource.java +++ /dev/null @@ -1,349 +0,0 @@ -package de.tum.cit.aet.artemis.programming.web.hestia; - -import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; - -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Set; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Profile; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import de.tum.cit.aet.artemis.core.exception.BadRequestAlertException; -import de.tum.cit.aet.artemis.core.exception.ConflictException; -import de.tum.cit.aet.artemis.core.repository.UserRepository; -import de.tum.cit.aet.artemis.core.security.annotations.EnforceAtLeastStudent; -import de.tum.cit.aet.artemis.core.security.annotations.enforceRoleInExercise.EnforceAtLeastEditorInExercise; -import de.tum.cit.aet.artemis.core.security.annotations.enforceRoleInExercise.EnforceAtLeastStudentInExercise; -import de.tum.cit.aet.artemis.core.security.annotations.enforceRoleInExercise.EnforceAtLeastTutorInExercise; -import de.tum.cit.aet.artemis.exercise.domain.Exercise; -import de.tum.cit.aet.artemis.exercise.repository.ExerciseRepository; -import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise; -import de.tum.cit.aet.artemis.programming.domain.hestia.CodeHint; -import de.tum.cit.aet.artemis.programming.domain.hestia.ExerciseHint; -import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseRepository; -import de.tum.cit.aet.artemis.programming.repository.hestia.ExerciseHintRepository; -import de.tum.cit.aet.artemis.programming.service.hestia.CodeHintService; -import de.tum.cit.aet.artemis.programming.service.hestia.ExerciseHintService; -import tech.jhipster.web.util.HeaderUtil; - -/** - * REST controller for managing {@link ExerciseHint}. - */ -@Profile(PROFILE_CORE) -@RestController -@RequestMapping("api/") -public class ExerciseHintResource { - - private static final String EXERCISE_HINT_ENTITY_NAME = "exerciseHint"; - - private static final String CODE_HINT_ENTITY_NAME = "codeHint"; - - private static final Logger log = LoggerFactory.getLogger(ExerciseHintResource.class); - - private final ExerciseHintService exerciseHintService; - - private final ExerciseHintRepository exerciseHintRepository; - - private final ProgrammingExerciseRepository programmingExerciseRepository; - - private final ExerciseRepository exerciseRepository; - - private final CodeHintService codeHintService; - - private final UserRepository userRepository; - - @Value("${jhipster.clientApp.name}") - private String applicationName; - - public ExerciseHintResource(ExerciseHintService exerciseHintService, ExerciseHintRepository exerciseHintRepository, ProgrammingExerciseRepository programmingExerciseRepository, - ExerciseRepository exerciseRepository, CodeHintService codeHintService, UserRepository userRepository) { - this.exerciseHintService = exerciseHintService; - this.exerciseHintRepository = exerciseHintRepository; - this.programmingExerciseRepository = programmingExerciseRepository; - this.exerciseRepository = exerciseRepository; - this.codeHintService = codeHintService; - this.userRepository = userRepository; - } - - /** - * {@code POST programming-exercises/:exerciseId/exercise-hints} : Create a new exerciseHint for an exercise. - * - * @param exerciseHint the exerciseHint to create - * @param exerciseId the exerciseId of the exercise of which to create the exerciseHint - * @return the {@link ResponseEntity} with status {@code 201 (Created)} and with body the new exerciseHint, - * or with status {@code 409 (Conflict)} if the exerciseId is invalid, - * @throws URISyntaxException if the Location URI syntax is incorrect. - */ - @PostMapping("programming-exercises/{exerciseId}/exercise-hints") - @EnforceAtLeastEditorInExercise - public ResponseEntity createExerciseHint(@RequestBody ExerciseHint exerciseHint, @PathVariable Long exerciseId) throws URISyntaxException { - log.debug("REST request to save ExerciseHint : {}", exerciseHint); - - // Reload the exercise from the database as we can't trust data from the client - Exercise exercise = exerciseRepository.findByIdElseThrow(exerciseId); - - if (exerciseHint instanceof CodeHint) { - throw new BadRequestAlertException("A code hint cannot be created manually.", CODE_HINT_ENTITY_NAME, "manualCodeHintOperation"); - } - if (exerciseHint.getExercise() == null) { - throw new ConflictException("An exercise hint can only be created if the exercise is defined.", EXERCISE_HINT_ENTITY_NAME, "exerciseNotDefined"); - } - - if (!exerciseHint.getExercise().getId().equals(exerciseId)) { - throw new ConflictException("An exercise hint can only be created if the exerciseIds match.", EXERCISE_HINT_ENTITY_NAME, "exerciseIdMismatch"); - } - - // Hints for exam exercises are not supported at the moment - if (exercise.isExamExercise()) { - throw new BadRequestAlertException("Exercise hints for exams are currently not supported", EXERCISE_HINT_ENTITY_NAME, "exerciseHintNotSupported"); - } - ExerciseHint result = exerciseHintRepository.save(exerciseHint); - return ResponseEntity.created(new URI("/api/programming-exercises/" + exerciseHint.getExercise().getId() + "/exercise-hints/" + result.getId())) - .headers(HeaderUtil.createEntityCreationAlert(applicationName, true, EXERCISE_HINT_ENTITY_NAME, result.getId().toString())).body(result); - } - - /** - * {@code PUT programming-exercises/:exerciseId/exercise-hints/:exerciseHintId} : Updates an existing exerciseHint. - * - * @param exerciseHint the exerciseHint to update - * @param exerciseId the exerciseId of the exercise of which to update the exerciseHint - * @param exerciseHintId the id to the exerciseHint - * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the updated exerciseHint, - * or with status {@code 409 (Conflict} if the exerciseHint or exerciseId are not valid, - * or with status {@code 500 (Internal Server Error)} if the exerciseHint couldn't be updated. - */ - @PutMapping("programming-exercises/{exerciseId}/exercise-hints/{exerciseHintId}") - @EnforceAtLeastEditorInExercise - public ResponseEntity updateExerciseHint(@RequestBody ExerciseHint exerciseHint, @PathVariable Long exerciseHintId, @PathVariable Long exerciseId) { - log.debug("REST request to update ExerciseHint : {}", exerciseHint); - - // Reload the exercise from the database as we can't trust data from the client - Exercise exercise = exerciseRepository.findByIdElseThrow(exerciseId); - var hintBeforeSaving = exerciseHintRepository.findByIdWithRelationsElseThrow(exerciseHintId); - - if (!exerciseHint.getClass().equals(hintBeforeSaving.getClass())) { - throw new BadRequestAlertException("A code hint cannot be converted to or from a normal hint.", CODE_HINT_ENTITY_NAME, "manualCodeHintOperation"); - } - - if (exerciseHint.getId() == null || !exerciseHintId.equals(exerciseHint.getId()) || exerciseHint.getExercise() == null) { - throw new ConflictException("An exercise hint can only be changed if it has an ID and if the exercise is not null.", EXERCISE_HINT_ENTITY_NAME, "exerciseNotDefined"); - } - - if (!exerciseHint.getExercise().getId().equals(exerciseId)) { - throw new ConflictException("An exercise hint can only be updated if the exerciseIds match.", EXERCISE_HINT_ENTITY_NAME, "exerciseIdsMismatch"); - } - - // Hints for exam exercises are not supported at the moment - if (exercise.isExamExercise()) { - throw new BadRequestAlertException("Exercise hints for exams are currently not supported", EXERCISE_HINT_ENTITY_NAME, "exerciseHintNotSupported"); - } - - if (exerciseHint instanceof CodeHint codeHint && codeHint.getSolutionEntries() != null) { - codeHintService.updateSolutionEntriesForCodeHint(codeHint); - } - exerciseHint.setExerciseHintActivations(hintBeforeSaving.getExerciseHintActivations()); - ExerciseHint result = exerciseHintRepository.save(exerciseHint); - return ResponseEntity.ok().headers(HeaderUtil.createEntityUpdateAlert(applicationName, true, EXERCISE_HINT_ENTITY_NAME, exerciseHint.getId().toString())).body(result); - } - - /** - * {@code GET programming-exercises/:exerciseId/exercise-hints/:exerciseHintId/title} : Returns the title of the hint with the given id - * - * @param exerciseHintId the id of the exerciseHint - * @param exerciseId the exerciseId of the exercise of which to retrieve the exerciseHints' title - * @return the title of the hint wrapped in an ResponseEntity or 404 Not Found if no hint with that id exists - * or with status {@code 409 (Conflict)} if the exerciseId is not valid. - */ - @GetMapping("programming-exercises/{exerciseId}/exercise-hints/{exerciseHintId}/title") - @EnforceAtLeastStudent - public ResponseEntity getHintTitle(@PathVariable Long exerciseId, @PathVariable Long exerciseHintId) { - var title = exerciseHintService.getExerciseHintTitle(exerciseId, exerciseHintId); - return title == null ? ResponseEntity.notFound().build() : ResponseEntity.ok(title); - } - - /** - * {@code GET programming-exercises/:exerciseId/exercise-hints/:exerciseHintId} : get the exerciseHint with the given id. - * - * @param exerciseHintId the id of the exerciseHint to retrieve. - * @param exerciseId the exerciseId of the exercise of which to retrieve the exerciseHint - * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the exerciseHint, - * or with status {@code 404 (Not Found)}, - * or with status {@code 409 (Conflict)} if the exerciseId is not valid. - */ - @GetMapping("programming-exercises/{exerciseId}/exercise-hints/{exerciseHintId}") - @EnforceAtLeastTutorInExercise - public ResponseEntity getExerciseHint(@PathVariable Long exerciseId, @PathVariable Long exerciseHintId) { - log.debug("REST request to get ExerciseHint : {}", exerciseHintId); - ProgrammingExercise exercise = programmingExerciseRepository.findByIdElseThrow(exerciseId); - if (exercise.isExamExercise()) { - // not allowed for exam exercises - throw new BadRequestAlertException("Exercise hints for exams are currently not supported", EXERCISE_HINT_ENTITY_NAME, "exerciseHintNotSupported"); - } - - var exerciseHint = exerciseHintRepository.findByIdWithRelationsElseThrow(exerciseHintId); - - if (!exerciseHint.getExercise().getId().equals(exerciseId)) { - throw new ConflictException("An exercise hint can only be retrieved if the exerciseIds match.", EXERCISE_HINT_ENTITY_NAME, "exerciseIdsMismatch"); - } - - return ResponseEntity.ok().body(exerciseHint); - } - - /** - * {@code GET programming-exercises/:exerciseId/exercise-hints} : get the exerciseHints of a provided exercise. - * - * @param exerciseId the exercise id of which to retrieve the exercise hints. - * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the exerciseHint, - * or with status {@code 404 (Not Found)}, - * or with status {@code 409 (Conflict)} if the exerciseId is not valid. - */ - @GetMapping("programming-exercises/{exerciseId}/exercise-hints") - @EnforceAtLeastTutorInExercise - public ResponseEntity> getExerciseHintsForExercise(@PathVariable Long exerciseId) { - log.debug("REST request to get ExerciseHints : {}", exerciseId); - var exerciseHints = exerciseHintRepository.findByExerciseIdWithRelations(exerciseId); - return ResponseEntity.ok(exerciseHints); - } - - /** - * {@code GET programming-exercises/:exerciseId/exercise-hints/activated} : get the exercise hints of a provided exercise that the user has activated - * - * @param exerciseId the exercise id of which to retrieve the exercise hints. - * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the exercise hints, - * or with status {@code 404 (Not Found)} - */ - @GetMapping("programming-exercises/{exerciseId}/exercise-hints/activated") - @EnforceAtLeastStudentInExercise - public ResponseEntity> getActivatedExerciseHintsForExercise(@PathVariable Long exerciseId) { - log.debug("REST request to get activated ExerciseHints : {}", exerciseId); - ProgrammingExercise exercise = programmingExerciseRepository.findByIdElseThrow(exerciseId); - if (exercise.isExamExercise()) { - // not allowed for exam exercises - throw new BadRequestAlertException("Exercise hints for exams are currently not supported", EXERCISE_HINT_ENTITY_NAME, "exerciseHintNotSupported"); - } - - var user = userRepository.getUserWithGroupsAndAuthorities(); - var exerciseHints = exerciseHintService.getActivatedExerciseHints(exercise, user); - return ResponseEntity.ok(exerciseHints); - } - - /** - * {@code GET programming-exercises/:exerciseId/available-exercise-hints} : get the available exercise hints. - * - * @param exerciseId the exerciseId of the exercise of which to retrieve the exercise hint - * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the exercise hints - */ - @GetMapping("programming-exercises/{exerciseId}/exercise-hints/available") - @EnforceAtLeastStudentInExercise - public ResponseEntity> getAvailableExerciseHintsForExercise(@PathVariable Long exerciseId) { - log.debug("REST request to get a CodeHint for programming exercise : {}", exerciseId); - ProgrammingExercise exercise = programmingExerciseRepository.findByIdElseThrow(exerciseId); - if (exercise.isExamExercise()) { - // not allowed for exam exercises - throw new BadRequestAlertException("Exercise hints for exams are currently not supported", EXERCISE_HINT_ENTITY_NAME, "exerciseHintNotSupported"); - } - - var user = userRepository.getUserWithGroupsAndAuthorities(); - var availableExerciseHints = exerciseHintService.getAvailableExerciseHints(exercise, user); - availableExerciseHints.forEach(ExerciseHint::removeContent); - - return ResponseEntity.ok().body(availableExerciseHints); - } - - /** - * {@code POST programming-exercises/:exerciseId/exercise-hints/:exerciseHintId/activate} - * Activates a single exercise hint of an exercise for the logged-in user - * - * @param exerciseId The id of the exercise of which to activate the exercise hint - * @param exerciseHintId The id of the exercise hint to activate - * @return The {@link ResponseEntity} with status {@code 200 (OK)} and with body the activated exercise hint with content - * or with status {@code 400 (BAD_REQUEST)} if the hint could not be activated - */ - @PostMapping("programming-exercises/{exerciseId}/exercise-hints/{exerciseHintId}/activate") - @EnforceAtLeastStudentInExercise - public ResponseEntity activateExerciseHint(@PathVariable Long exerciseId, @PathVariable Long exerciseHintId) { - log.debug("REST request to activate ExerciseHint : {}", exerciseHintId); - var exercise = programmingExerciseRepository.findByIdElseThrow(exerciseId); - var user = userRepository.getUserWithGroupsAndAuthorities(); - - var exerciseHint = exerciseHintRepository.findByIdWithRelationsElseThrow(exerciseHintId); - - if (!exerciseHint.getExercise().getId().equals(exercise.getId())) { - throw new ConflictException("An exercise hint can only be deleted if the exerciseIds match.", EXERCISE_HINT_ENTITY_NAME, "exerciseIdsMismatch"); - } - - if (exerciseHintService.activateHint(exerciseHint, user)) { - return ResponseEntity.ok(exerciseHint); - } - else { - throw new BadRequestAlertException("Unable to activate exercise hint", EXERCISE_HINT_ENTITY_NAME, "exerciseHintIdActivationFailed"); - } - } - - /** - * {@code POST programming-exercises/:exerciseId/exercise-hints/:exerciseHintId/rating/:ratingValue}: Rates an exercise hint - * - * @param exerciseId The id of the exercise of which to activate the exercise hint - * @param exerciseHintId The id of the exercise hint to activate - * @param ratingValue The value of the rating - * @return The {@link ResponseEntity} with status {@code 200 (OK)} - */ - @PostMapping("programming-exercises/{exerciseId}/exercise-hints/{exerciseHintId}/rating/{ratingValue}") - @EnforceAtLeastStudentInExercise - public ResponseEntity rateExerciseHint(@PathVariable Long exerciseId, @PathVariable Long exerciseHintId, @PathVariable Integer ratingValue) { - log.debug("REST request to rate ExerciseHint : {}", exerciseHintId); - var user = userRepository.getUserWithGroupsAndAuthorities(); - - var exerciseHint = exerciseHintRepository.findByIdWithRelationsElseThrow(exerciseHintId); - if (!exerciseHint.getExercise().getId().equals(exerciseId)) { - throw new ConflictException("An exercise hint can only be deleted if the exerciseIds match.", EXERCISE_HINT_ENTITY_NAME, "exerciseIdsMismatch"); - } - - exerciseHintService.rateExerciseHint(exerciseHint, user, ratingValue); - - return ResponseEntity.ok().build(); - } - - /** - * {@code DELETE programming-exercises/:exerciseId/exercise-hints/:exerciseHintId} : delete the exerciseHint with given id. - * - * @param exerciseHintId the id of the exerciseHint to delete - * @param exerciseId the exercise id of which to delete the exercise hint - * @return the {@link ResponseEntity} with status {@code 204 (NO_CONTENT)}, - * or with status {@code 409 (Conflict)} if the exerciseId is not valid. - */ - @DeleteMapping("programming-exercises/{exerciseId}/exercise-hints/{exerciseHintId}") - @EnforceAtLeastEditorInExercise - public ResponseEntity deleteExerciseHint(@PathVariable Long exerciseId, @PathVariable Long exerciseHintId) { - log.debug("REST request to delete ExerciseHint : {}", exerciseHintId); - var exerciseHint = exerciseHintRepository.findByIdElseThrow(exerciseHintId); - - if (!exerciseHint.getExercise().getId().equals(exerciseId)) { - throw new ConflictException("An exercise hint can only be deleted if the exerciseIds match.", EXERCISE_HINT_ENTITY_NAME, "exerciseIdsMismatch"); - } - - String entityName; - - if (exerciseHint instanceof CodeHint codeHint) { - codeHintService.deleteCodeHint(codeHint); - entityName = CODE_HINT_ENTITY_NAME; - } - else { - exerciseHintRepository.deleteById(exerciseHintId); - entityName = EXERCISE_HINT_ENTITY_NAME; - } - return ResponseEntity.noContent().headers(HeaderUtil.createEntityDeletionAlert(applicationName, true, entityName, exerciseHintId.toString())).build(); - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/web/hestia/ProgrammingExerciseSolutionEntryResource.java b/src/main/java/de/tum/cit/aet/artemis/programming/web/hestia/ProgrammingExerciseSolutionEntryResource.java deleted file mode 100644 index c18e84806672..000000000000 --- a/src/main/java/de/tum/cit/aet/artemis/programming/web/hestia/ProgrammingExerciseSolutionEntryResource.java +++ /dev/null @@ -1,337 +0,0 @@ -package de.tum.cit.aet.artemis.programming.web.hestia; - -import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; - -import java.net.URI; -import java.net.URISyntaxException; -import java.util.List; -import java.util.Set; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Profile; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import de.tum.cit.aet.artemis.core.exception.ConflictException; -import de.tum.cit.aet.artemis.core.exception.InternalServerErrorException; -import de.tum.cit.aet.artemis.core.security.Role; -import de.tum.cit.aet.artemis.core.security.annotations.EnforceAtLeastEditor; -import de.tum.cit.aet.artemis.core.security.annotations.EnforceAtLeastStudent; -import de.tum.cit.aet.artemis.core.security.annotations.EnforceAtLeastTutor; -import de.tum.cit.aet.artemis.core.service.AuthorizationCheckService; -import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise; -import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseTestCase; -import de.tum.cit.aet.artemis.programming.domain.hestia.CodeHint; -import de.tum.cit.aet.artemis.programming.domain.hestia.ProgrammingExerciseSolutionEntry; -import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseRepository; -import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseTestCaseRepository; -import de.tum.cit.aet.artemis.programming.repository.hestia.CodeHintRepository; -import de.tum.cit.aet.artemis.programming.repository.hestia.ProgrammingExerciseSolutionEntryRepository; -import de.tum.cit.aet.artemis.programming.service.hestia.behavioral.BehavioralSolutionEntryGenerationException; -import de.tum.cit.aet.artemis.programming.service.hestia.behavioral.BehavioralTestCaseService; -import de.tum.cit.aet.artemis.programming.service.hestia.structural.StructuralSolutionEntryGenerationException; -import de.tum.cit.aet.artemis.programming.service.hestia.structural.StructuralTestCaseService; -import tech.jhipster.web.util.HeaderUtil; - -/** - * REST controller for managing {@link ProgrammingExerciseSolutionEntry}. - */ -@Profile(PROFILE_CORE) -@RestController -@RequestMapping("api/") -public class ProgrammingExerciseSolutionEntryResource { - - private static final Logger log = LoggerFactory.getLogger(ProgrammingExerciseSolutionEntryResource.class); - - private static final String ENTITY_NAME = "programmingExerciseSolutionEntry"; - - @Value("${jhipster.clientApp.name}") - private String applicationName; - - private final ProgrammingExerciseSolutionEntryRepository programmingExerciseSolutionEntryRepository; - - private final ProgrammingExerciseRepository programmingExerciseRepository; - - private final CodeHintRepository codeHintRepository; - - private final ProgrammingExerciseTestCaseRepository programmingExerciseTestCaseRepository; - - private final AuthorizationCheckService authCheckService; - - private final StructuralTestCaseService structuralTestCaseService; - - private final BehavioralTestCaseService behavioralTestCaseService; - - public ProgrammingExerciseSolutionEntryResource(ProgrammingExerciseSolutionEntryRepository programmingExerciseSolutionEntryRepository, - ProgrammingExerciseRepository programmingExerciseRepository, CodeHintRepository codeHintRepository, - ProgrammingExerciseTestCaseRepository programmingExerciseTestCaseRepository, AuthorizationCheckService authCheckService, - StructuralTestCaseService structuralTestCaseService, BehavioralTestCaseService behavioralTestCaseService) { - this.programmingExerciseSolutionEntryRepository = programmingExerciseSolutionEntryRepository; - this.programmingExerciseRepository = programmingExerciseRepository; - this.codeHintRepository = codeHintRepository; - this.programmingExerciseTestCaseRepository = programmingExerciseTestCaseRepository; - this.authCheckService = authCheckService; - this.structuralTestCaseService = structuralTestCaseService; - this.behavioralTestCaseService = behavioralTestCaseService; - } - - /** - * GET programming-exercises/:exerciseId/solution-entries/:solutionEntryId : Get the solution entry with test cases and programming exercise - * - * @param exerciseId of the exercise - * @param solutionEntryId of the solution entry - * @return the {@link ResponseEntity} with status {@code 200} and with body the solution entries with test cases and exercise, - * or with status {@code 409 (Conflict)} if the exerciseId or solutionEntryId are not valid. - */ - @GetMapping("programming-exercises/{exerciseId}/solution-entries/{solutionEntryId}") - @EnforceAtLeastTutor - public ResponseEntity getSolutionEntry(@PathVariable Long exerciseId, @PathVariable Long solutionEntryId) { - log.debug("REST request to retrieve SolutionEntry : {}", solutionEntryId); - // Reload the exercise from the database as we can't trust data from the client - ProgrammingExercise exercise = programmingExerciseRepository.findByIdElseThrow(exerciseId); - authCheckService.checkHasAtLeastRoleForExerciseElseThrow(Role.TEACHING_ASSISTANT, exercise, null); - - ProgrammingExerciseSolutionEntry solutionEntry = programmingExerciseSolutionEntryRepository.findByIdWithTestCaseAndProgrammingExerciseElseThrow(solutionEntryId); - - if (!exerciseId.equals(solutionEntry.getTestCase().getExercise().getId())) { - throw new ConflictException("A solution entry can only be retrieved if the exercise match", ENTITY_NAME, "exerciseIdsMismatch"); - } - return ResponseEntity.ok(solutionEntry); - } - - /** - * GET programming-exercises/{exerciseId}/solution-entries: Get all solution entries with test cases for a programming exercise - * - * @param exerciseId of the exercise - * @return the {@link ResponseEntity} with status {@code 200} and with body the solution entries with test cases. - */ - @GetMapping("programming-exercises/{exerciseId}/solution-entries") - @EnforceAtLeastEditor - public ResponseEntity> getAllSolutionEntries(@PathVariable Long exerciseId) { - ProgrammingExercise exercise = programmingExerciseRepository.findByIdElseThrow(exerciseId); - authCheckService.checkHasAtLeastRoleForExerciseElseThrow(Role.EDITOR, exercise, null); - - var result = programmingExerciseSolutionEntryRepository.findByExerciseIdWithTestCases(exercise.getId()); - return ResponseEntity.ok(result); - } - - /** - * GET programming-exercises/:exerciseId/code-hints/:codeHintId/solution-entries : Get all solution entries for a given code hint - * - * @param exerciseId of the exercise - * @param codeHintId of the code hint - * @return the {@link ResponseEntity} with status {@code 200} and with body the solution entries, - * or with status {@code 409 (Conflict)} if the exerciseId or codeHintId are not valid. - */ - @GetMapping("programming-exercises/{exerciseId}/code-hints/{codeHintId}/solution-entries") - @EnforceAtLeastStudent - public ResponseEntity> getSolutionEntriesForCodeHint(@PathVariable Long exerciseId, @PathVariable Long codeHintId) { - log.debug("REST request to retrieve SolutionEntry for CodeHint with id : {}", codeHintId); - // Reload the exercise from the database as we can't trust data from the client - ProgrammingExercise exercise = programmingExerciseRepository.findByIdElseThrow(exerciseId); - authCheckService.checkHasAtLeastRoleForExerciseElseThrow(Role.STUDENT, exercise, null); - - CodeHint codeHint = codeHintRepository.findByIdElseThrow(codeHintId); - if (!exercise.getId().equals(codeHint.getExercise().getId())) { - throw new ConflictException("A solution entry can only be retrieved if the code hint belongs to the exercise", ENTITY_NAME, "exerciseIdsMismatch"); - } - - Set solutionEntries = programmingExerciseSolutionEntryRepository.findByCodeHintId(codeHintId); - return ResponseEntity.ok(solutionEntries); - } - - /** - * GET programming-exercises/:exerciseId/test-cases/:testCaseId/solution-entries : Get all solution entries for a given test case - * - * @param exerciseId of the exercise - * @param testCaseId of the test case - * @return the {@link ResponseEntity} with status {@code 200} and with body the solution entries, - * or with status {@code 409 (Conflict)} if the exerciseId or testCaseId are not valid. - */ - @GetMapping("programming-exercises/{exerciseId}/test-cases/{testCaseId}/solution-entries") - @EnforceAtLeastStudent - public ResponseEntity> getSolutionEntriesForTestCase(@PathVariable Long exerciseId, @PathVariable Long testCaseId) { - log.debug("REST request to retrieve SolutionEntry for ProgrammingExerciseTestCase with id : {}", testCaseId); - // Reload the exercise from the database as we can't trust data from the client - ProgrammingExercise exercise = programmingExerciseRepository.findByIdElseThrow(exerciseId); - authCheckService.checkHasAtLeastRoleForExerciseElseThrow(Role.STUDENT, exercise, null); - - ProgrammingExerciseTestCase testCase = programmingExerciseTestCaseRepository.findByIdWithExerciseElseThrow(testCaseId); - if (!exercise.getId().equals(testCase.getExercise().getId())) { - throw new ConflictException("A solution entry can only be retrieved if the test case belongs to the exercise", ENTITY_NAME, "exerciseIdsMismatch"); - } - - Set solutionEntries = programmingExerciseSolutionEntryRepository.findByTestCaseId(testCaseId); - return ResponseEntity.ok(solutionEntries); - } - - /** - * POST programming-exercises/:exerciseId/test-cases/:testCaseId/solution-entries : Create a solution entry for a test case - * - * @param exerciseId of the exercise - * @param testCaseId of the test case - * @param programmingExerciseSolutionEntry the solution entry to be created - * @return the {@link ResponseEntity} with status {@code 201} and with body the created solution entry, - * or with status {@code 409 (Conflict)} if the exerciseId, testcaseId, or solution entry are not valid. - */ - @PostMapping("programming-exercises/{exerciseId}/test-cases/{testCaseId}/solution-entries") - @EnforceAtLeastEditor - public ResponseEntity createSolutionEntryForTestCase(@PathVariable Long exerciseId, @PathVariable Long testCaseId, - @RequestBody ProgrammingExerciseSolutionEntry programmingExerciseSolutionEntry) throws URISyntaxException { - log.debug("REST request to create SolutionEntry : {}", programmingExerciseSolutionEntry); - ProgrammingExercise exercise = programmingExerciseRepository.findByIdElseThrow(exerciseId); - authCheckService.checkHasAtLeastRoleForExerciseElseThrow(Role.EDITOR, exercise, null); - - ProgrammingExerciseTestCase testCase = programmingExerciseTestCaseRepository.findByIdWithExerciseElseThrow(testCaseId); - checkExerciseContainsTestCaseElseThrow(exercise, testCase); - - ProgrammingExerciseSolutionEntry result = programmingExerciseSolutionEntryRepository.save(programmingExerciseSolutionEntry); - return ResponseEntity.created(new URI("/api/programming-exercises/" + exerciseId + "/solution-entries/" + result.getId())) - .headers(HeaderUtil.createEntityCreationAlert(applicationName, true, ENTITY_NAME, result.getId().toString())).body(result); - } - - /** - * PUT programming-exercises/:exerciseId/test-cases/:testCaseId/solution-entries/:solutionEntryId : Update a solution entry - * - * @param exerciseId of the exercise - * @param testCaseId of the test case - * @param solutionEntryId of the solution entry - * @param solutionEntry the updated solution entry - * @return the {@link ResponseEntity} with status {@code 200} and with body the updated solution entry, - * or with status {@code 409 (Conflict)} if the exerciseId, testcaseId, solutionEntryId, or solution entry are not valid. - */ - @PutMapping("programming-exercises/{exerciseId}/test-cases/{testCaseId}/solution-entries/{solutionEntryId}") - @EnforceAtLeastEditor - public ResponseEntity updateSolutionEntry(@PathVariable Long exerciseId, @PathVariable Long testCaseId, @PathVariable Long solutionEntryId, - @RequestBody ProgrammingExerciseSolutionEntry solutionEntry) { - log.debug("REST request to update SolutionEntry : {}", solutionEntry); - ProgrammingExercise exercise = programmingExerciseRepository.findByIdElseThrow(exerciseId); - authCheckService.checkHasAtLeastRoleForExerciseElseThrow(Role.EDITOR, exercise, null); - - ProgrammingExerciseTestCase testCase = programmingExerciseTestCaseRepository.findByIdWithExerciseElseThrow(testCaseId); - ProgrammingExerciseSolutionEntry solutionEntryBeforeSaving = programmingExerciseSolutionEntryRepository - .findByIdWithTestCaseAndProgrammingExerciseElseThrow(solutionEntryId); - - checkExerciseContainsTestCaseElseThrow(exercise, testCase); - if (!solutionEntryId.equals(solutionEntryBeforeSaving.getId())) { - throw new ConflictException("A solution entry can only be updated if the solutionEntryIds match", ENTITY_NAME, "solutionEntryError"); - } - - ProgrammingExerciseSolutionEntry solutionEntryAfterSaving = programmingExerciseSolutionEntryRepository.save(solutionEntry); - return ResponseEntity.ok().headers(HeaderUtil.createEntityUpdateAlert(applicationName, true, ENTITY_NAME, solutionEntryAfterSaving.getId().toString())) - .body(solutionEntryAfterSaving); - } - - /** - * DELETE programming-exercises/:exerciseId/test-cases/:testCaseId/solution-entries/:solutionEntryId : Delete a solution entry - * - * @param exerciseId of the exercise - * @param testCaseId of the test case - * @param solutionEntryId of the solution entry that is to be deleted - * @return the {@link ResponseEntity} with status {@code 204}, - * or with status {@code 409 (Conflict)} if the exerciseId, testcaseId, or solutionEntryId are not valid. - */ - @DeleteMapping("programming-exercises/{exerciseId}/test-cases/{testCaseId}/solution-entries/{solutionEntryId}") - @EnforceAtLeastEditor - public ResponseEntity deleteSolutionEntry(@PathVariable Long exerciseId, @PathVariable Long testCaseId, @PathVariable Long solutionEntryId) { - log.debug("REST request to delete SolutionEntry with id : {}", solutionEntryId); - ProgrammingExercise exercise = programmingExerciseRepository.findByIdElseThrow(exerciseId); - authCheckService.checkHasAtLeastRoleForExerciseElseThrow(Role.EDITOR, exercise, null); - - ProgrammingExerciseTestCase testCase = programmingExerciseTestCaseRepository.findByIdWithExerciseElseThrow(testCaseId); - ProgrammingExerciseSolutionEntry solutionEntry = programmingExerciseSolutionEntryRepository.findByIdWithTestCaseAndProgrammingExerciseElseThrow(solutionEntryId); - - checkExerciseContainsTestCaseElseThrow(exercise, testCase); - checkTestCaseContainsSolutionEntryElseThrow(testCase, solutionEntry); - - programmingExerciseSolutionEntryRepository.deleteById(solutionEntryId); - return ResponseEntity.noContent().headers(HeaderUtil.createEntityDeletionAlert(applicationName, true, ENTITY_NAME, solutionEntry.getId().toString())).build(); - } - - /** - * DELETE programming-exercises/:exerciseId/solution-entries: Delete all solution entries for a programming exercise - * - * @param exerciseId of the exercise - * @return the {@link ResponseEntity} with status {@code 204}, - * or with status {@code 404} if the exerciseId is not valid. - */ - @DeleteMapping("programming-exercises/{exerciseId}/solution-entries") - @EnforceAtLeastEditor - public ResponseEntity deleteAllSolutionEntriesForExercise(@PathVariable Long exerciseId) { - log.debug("REST request to delete all SolutionEntries for exercise with id: {}", exerciseId); - ProgrammingExercise exercise = programmingExerciseRepository.findByIdElseThrow(exerciseId); - authCheckService.checkHasAtLeastRoleForExerciseElseThrow(Role.EDITOR, exercise, null); - - var entriesToDelete = programmingExerciseSolutionEntryRepository.findByExerciseIdWithTestCases(exercise.getId()); - - programmingExerciseSolutionEntryRepository.deleteAll(entriesToDelete); - return ResponseEntity.noContent().build(); - } - - /** - * POST programming-exercises/:exerciseId/structural-solution-entries : Create the structural solution entries for a programming exercise - * - * @param exerciseId of the exercise - * @return the {@link ResponseEntity} with status {@code 200} and with body the created solution entries, - */ - @PostMapping("programming-exercises/{exerciseId}/structural-solution-entries") - @EnforceAtLeastEditor - public ResponseEntity> createStructuralSolutionEntries(@PathVariable Long exerciseId) { - log.debug("REST request to create structural solution entries"); - ProgrammingExercise exercise = programmingExerciseRepository.findByIdWithTemplateAndSolutionParticipationElseThrow(exerciseId); - authCheckService.checkHasAtLeastRoleForExerciseElseThrow(Role.EDITOR, exercise, null); - - try { - var solutionEntries = structuralTestCaseService.generateStructuralSolutionEntries(exercise); - return ResponseEntity.ok(solutionEntries); - } - catch (StructuralSolutionEntryGenerationException e) { - log.error("Unable to create structural solution entries", e); - throw new InternalServerErrorException(e.getMessage()); - } - } - - /** - * POST programming-exercises/:exerciseId/behavioral-solution-entries : Create the behavioral solution entries for a programming exercise - * - * @param exerciseId of the exercise - * @return the {@link ResponseEntity} with status {@code 200} and with body the created solution entries, - */ - @PostMapping("programming-exercises/{exerciseId}/behavioral-solution-entries") - @EnforceAtLeastEditor - public ResponseEntity> createBehavioralSolutionEntries(@PathVariable Long exerciseId) { - log.debug("REST request to create behavioral solution entries"); - ProgrammingExercise exercise = programmingExerciseRepository.findByIdWithTemplateAndSolutionParticipationTeamAssignmentConfigCategoriesAndBuildConfigElseThrow(exerciseId); - authCheckService.checkHasAtLeastRoleForExerciseElseThrow(Role.EDITOR, exercise, null); - - try { - var solutionEntries = behavioralTestCaseService.generateBehavioralSolutionEntries(exercise); - return ResponseEntity.ok(solutionEntries); - } - catch (BehavioralSolutionEntryGenerationException e) { - log.error("Unable to create behavioral solution entries", e); - throw new InternalServerErrorException(e.getMessage()); - } - } - - private void checkExerciseContainsTestCaseElseThrow(ProgrammingExercise exercise, ProgrammingExerciseTestCase testCase) { - if (!exercise.getId().equals(testCase.getExercise().getId())) { - throw new ConflictException("The test case of the solution entry does not belong to the exercise.", ENTITY_NAME, "exerciseIdsMismatch"); - } - } - - private void checkTestCaseContainsSolutionEntryElseThrow(ProgrammingExerciseTestCase testCase, ProgrammingExerciseSolutionEntry solutionEntry) { - if (solutionEntry.getTestCase() == null || !testCase.getId().equals(solutionEntry.getTestCase().getId())) { - throw new ConflictException("The test case of the solution entry does not belong to the solution entry.", ENTITY_NAME, "solutionEntryError"); - } - } -} diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/web/localci/AeolusTemplateResource.java b/src/main/java/de/tum/cit/aet/artemis/programming/web/localci/AeolusTemplateResource.java index 9e5530e94813..a20ce1b7c163 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/web/localci/AeolusTemplateResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/web/localci/AeolusTemplateResource.java @@ -21,13 +21,13 @@ import de.tum.cit.aet.artemis.core.security.annotations.EnforceAtLeastEditor; import de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage; import de.tum.cit.aet.artemis.programming.domain.ProjectType; +import de.tum.cit.aet.artemis.programming.dto.aeolus.Windfile; import de.tum.cit.aet.artemis.programming.service.BuildScriptProviderService; import de.tum.cit.aet.artemis.programming.service.aeolus.AeolusTemplateService; -import de.tum.cit.aet.artemis.programming.service.aeolus.Windfile; /** * Service for retrieving aeolus template files based on the programming language, project type, and - * the different options (static analysis, sequential runs, test coverage) as well as the default + * the different options (static analysis, sequential runs) as well as the default * image for the programming language and project type for the artemis instance. */ @Profile("aeolus | localci") @@ -61,21 +61,19 @@ public AeolusTemplateResource(AeolusTemplateService aeolusTemplateService, Build * @param projectType The project type for which the template file should be returned. If omitted, a default depending on the language will be used. * @param staticAnalysis Whether the static analysis template should be used * @param sequentialRuns Whether the sequential runs template should be used - * @param testCoverage Whether the test coverage template should be used * @return The requested file, or 404 if the file doesn't exist */ @GetMapping({ "templates/{language}/{projectType}", "templates/{language}" }) @EnforceAtLeastEditor public ResponseEntity getAeolusTemplate(@PathVariable ProgrammingLanguage language, @PathVariable Optional projectType, @RequestParam(value = "staticAnalysis", defaultValue = "false") boolean staticAnalysis, - @RequestParam(value = "sequentialRuns", defaultValue = "false") boolean sequentialRuns, - @RequestParam(value = "testCoverage", defaultValue = "false") boolean testCoverage) { - log.debug("REST request to get aeolus template for programming language {} and project type {}, static Analysis: {}, sequential Runs {}, testCoverage: {}", language, - projectType, staticAnalysis, sequentialRuns, testCoverage); + @RequestParam(value = "sequentialRuns", defaultValue = "false") boolean sequentialRuns) { + log.debug("REST request to get aeolus template for programming language {} and project type {}, static Analysis: {}, sequential Runs {}", language, projectType, + staticAnalysis, sequentialRuns); String projectTypePrefix = projectType.map(type -> type.name().toLowerCase()).orElse(""); - return getAeolusTemplateFileContentWithResponse(language, projectTypePrefix, staticAnalysis, sequentialRuns, testCoverage); + return getAeolusTemplateFileContentWithResponse(language, projectTypePrefix, staticAnalysis, sequentialRuns); } /** @@ -88,21 +86,19 @@ public ResponseEntity getAeolusTemplate(@PathVariable ProgrammingLanguag * @param projectType The project type for which the template file should be returned. If omitted, a default depending on the language will be used. * @param staticAnalysis Whether the static analysis template should be used * @param sequentialRuns Whether the sequential runs template should be used - * @param testCoverage Whether the test coverage template should be used * @return The requested file, or 404 if the file doesn't exist */ @GetMapping({ "template-scripts/{language}/{projectType}", "template-scripts/{language}" }) @EnforceAtLeastEditor public ResponseEntity getAeolusTemplateScript(@PathVariable ProgrammingLanguage language, @PathVariable Optional projectType, @RequestParam(value = "staticAnalysis", defaultValue = "false") boolean staticAnalysis, - @RequestParam(value = "sequentialRuns", defaultValue = "false") boolean sequentialRuns, - @RequestParam(value = "testCoverage", defaultValue = "false") boolean testCoverage) { - log.debug("REST request to get aeolus template for programming language {} and project type {}, static Analysis: {}, sequential Runs {}, testCoverage: {}", language, - projectType, staticAnalysis, sequentialRuns, testCoverage); + @RequestParam(value = "sequentialRuns", defaultValue = "false") boolean sequentialRuns) { + log.debug("REST request to get aeolus template script for programming language {} and project type {}, static Analysis: {}, sequential Runs {}", language, projectType, + staticAnalysis, sequentialRuns); String projectTypePrefix = projectType.map(type -> type.name().toLowerCase()).orElse(""); - return getAeolusTemplateScriptWithResponse(language, projectTypePrefix, staticAnalysis, sequentialRuns, testCoverage); + return getAeolusTemplateScriptWithResponse(language, projectTypePrefix, staticAnalysis, sequentialRuns); } /** @@ -115,17 +111,16 @@ public ResponseEntity getAeolusTemplateScript(@PathVariable ProgrammingL * @param projectTypePrefix The project type for which the template file should be returned. If omitted, a default depending on the language will be used. * @param staticAnalysis Whether the static analysis template should be used * @param sequentialRuns Whether the sequential runs template should be used - * @param testCoverage Whether the test coverage template should be used * @return The requested file, or 404 if the file doesn't exist */ - private ResponseEntity getAeolusTemplateFileContentWithResponse(ProgrammingLanguage language, String projectTypePrefix, boolean staticAnalysis, boolean sequentialRuns, - boolean testCoverage) { + private ResponseEntity getAeolusTemplateFileContentWithResponse(ProgrammingLanguage language, String projectTypePrefix, boolean staticAnalysis, + boolean sequentialRuns) { try { Optional optionalProjectType = Optional.empty(); if (!projectTypePrefix.isEmpty()) { optionalProjectType = Optional.of(ProjectType.valueOf(projectTypePrefix.toUpperCase())); } - Windfile windfile = aeolusTemplateService.getWindfileFor(language, optionalProjectType, staticAnalysis, sequentialRuns, testCoverage); + Windfile windfile = aeolusTemplateService.getWindfileFor(language, optionalProjectType, staticAnalysis, sequentialRuns); if (windfile == null) { return new ResponseEntity<>(null, null, HttpStatus.NOT_FOUND); } @@ -148,17 +143,15 @@ private ResponseEntity getAeolusTemplateFileContentWithResponse(Programm * @param projectTypePrefix The project type for which the template file should be returned. If omitted, a default depending on the language will be used. * @param staticAnalysis Whether the static analysis template should be used * @param sequentialRuns Whether the sequential runs template should be used - * @param testCoverage Whether the test coverage template should be used * @return The requested file, or 404 if the file doesn't exist */ - private ResponseEntity getAeolusTemplateScriptWithResponse(ProgrammingLanguage language, String projectTypePrefix, boolean staticAnalysis, boolean sequentialRuns, - boolean testCoverage) { + private ResponseEntity getAeolusTemplateScriptWithResponse(ProgrammingLanguage language, String projectTypePrefix, boolean staticAnalysis, boolean sequentialRuns) { try { Optional optionalProjectType = Optional.empty(); if (!projectTypePrefix.isEmpty()) { optionalProjectType = Optional.of(ProjectType.valueOf(projectTypePrefix.toUpperCase())); } - String script = buildScriptProviderService.getScriptFor(language, optionalProjectType, staticAnalysis, sequentialRuns, testCoverage); + String script = buildScriptProviderService.getScriptFor(language, optionalProjectType, staticAnalysis, sequentialRuns); HttpHeaders responseHeaders = new HttpHeaders(); responseHeaders.setContentType(MediaType.TEXT_PLAIN); return new ResponseEntity<>(script, responseHeaders, HttpStatus.OK); diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/web/localci/BuildLogResource.java b/src/main/java/de/tum/cit/aet/artemis/programming/web/localci/BuildLogResource.java index c8fd18e69fca..ca33dd04d34e 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/web/localci/BuildLogResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/web/localci/BuildLogResource.java @@ -2,6 +2,8 @@ import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_LOCALCI; +import java.util.List; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Profile; @@ -16,6 +18,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import de.tum.cit.aet.artemis.buildagent.dto.BuildLogDTO; import de.tum.cit.aet.artemis.core.security.annotations.EnforceAtLeastEditor; import de.tum.cit.aet.artemis.programming.service.BuildLogEntryService; @@ -52,4 +55,25 @@ public ResponseEntity getBuildLogForBuildJob(@PathVariable String buil responseHeaders.setContentDispositionFormData("attachment", "build-" + buildJobId + ".log"); return new ResponseEntity<>(buildLog, responseHeaders, HttpStatus.OK); } + + /** + * GET /build-log/{buildJobId}/entries : get the build log entries for a given result + * + * @param buildJobId the id of the build job for which to retrieve the build log entries + * @return the ResponseEntity with status 200 (OK) and the build log entries in the body, or with status 404 (Not Found) if the build log entries could not be found + */ + @GetMapping("build-log/{buildJobId}/entries") + @EnforceAtLeastEditor + public ResponseEntity> getBuildLogEntriesForBuildJob(@PathVariable String buildJobId) { + FileSystemResource buildLog = buildLogEntryService.retrieveBuildLogsFromFileForBuildJob(buildJobId); + if (buildLog == null) { + return ResponseEntity.notFound().build(); + } + + var buildLogEntries = buildLogEntryService.parseBuildLogEntries(buildLog); + if (buildLogEntries == null || buildLogEntries.isEmpty()) { + return ResponseEntity.notFound().build(); + } + return ResponseEntity.ok(buildLogEntries); + } } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/web/repository/RepositoryProgrammingExerciseParticipationResource.java b/src/main/java/de/tum/cit/aet/artemis/programming/web/repository/RepositoryProgrammingExerciseParticipationResource.java index 6cb9df0b10ee..28515ba970bb 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/web/repository/RepositoryProgrammingExerciseParticipationResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/web/repository/RepositoryProgrammingExerciseParticipationResource.java @@ -7,8 +7,6 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; import jakarta.servlet.http.HttpServletRequest; @@ -294,24 +292,6 @@ public ResponseEntity> getFilesWithContent(@PathVariable Lon }); } - /** - * GET /repository/{participationId}/file-names: Gets the file names of the repository - * - * @param participationId participation of the student/template/solution - * @return the ResponseEntity with status 200 (OK) and a set of file names - */ - @GetMapping(value = "repository/{participationId}/file-names", produces = MediaType.APPLICATION_JSON_VALUE) - @EnforceAtLeastTutor - public ResponseEntity> getFileNames(@PathVariable Long participationId) { - return super.executeAndCheckForExceptions(() -> { - Repository repository = getRepository(participationId, RepositoryActionType.READ, true); - var nonFolderFileNames = super.repositoryService.getFiles(repository).entrySet().stream().filter(mapEntry -> mapEntry.getValue().equals(FileType.FILE)) - .map(Map.Entry::getKey).collect(Collectors.toSet()); - - return new ResponseEntity<>(nonFolderFileNames, HttpStatus.OK); - }); - } - @Override @PostMapping(value = "repository/{participationId}/file", produces = MediaType.APPLICATION_JSON_VALUE) @FeatureToggle(Feature.ProgrammingExercises) diff --git a/src/main/resources/config/application.yml b/src/main/resources/config/application.yml index 5a2441c2f919..82611f9ceac2 100644 --- a/src/main/resources/config/application.yml +++ b/src/main/resources/config/application.yml @@ -22,6 +22,8 @@ logging: org.springframework.web.socket.config: INFO liquibase: INFO de.jplag.Submission: ERROR + de.jplag.logging: WARN + de.jplag.reporting: WARN logback: rollingpolicy: max-history: 90 diff --git a/src/main/resources/templates/aeolus/java/plain_gradle_static_coverage.sh b/src/main/resources/templates/aeolus/java/plain_gradle_static_coverage.sh deleted file mode 100644 index ee2bfdea3d9b..000000000000 --- a/src/main/resources/templates/aeolus/java/plain_gradle_static_coverage.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env bash -set -e -export AEOLUS_INITIAL_DIRECTORY=${PWD} -tests () { - echo '⚙️ executing tests' - chmod +x ./gradlew - ./gradlew clean test tiaTests --run-all-tests -} - -static_code_analysis () { - echo '⚙️ executing static_code_analysis' - ./gradlew check -x test -} - -final_aeolus_post_action () { - set +e # from now on, we don't exit on errors - echo '⚙️ executing final_aeolus_post_action' - cd "${AEOLUS_INITIAL_DIRECTORY}" - static_code_analysis -} - -main () { - if [[ "${1}" == "aeolus_sourcing" ]]; then - return 0 # just source to use the methods in the subshell, no execution - fi - local _script_name - _script_name=${BASH_SOURCE[0]:-$0} - trap final_aeolus_post_action EXIT - - cd "${AEOLUS_INITIAL_DIRECTORY}" - bash -c "source ${_script_name} aeolus_sourcing; tests" -} - -main "${@}" diff --git a/src/main/resources/templates/aeolus/java/plain_gradle_static_coverage.yaml b/src/main/resources/templates/aeolus/java/plain_gradle_static_coverage.yaml deleted file mode 100644 index d9d319f2e49f..000000000000 --- a/src/main/resources/templates/aeolus/java/plain_gradle_static_coverage.yaml +++ /dev/null @@ -1,29 +0,0 @@ -api: v0.0.1 -actions: - - name: tests - script: |- - chmod +x ./gradlew - ./gradlew clean test tiaTests --run-all-tests - runAlways: false - - name: static_code_analysis - script: ./gradlew check -x test - runAlways: true - results: - - name: spotbugs - path: target/spotbugsXml.xml - type: static-code-analysis - - name: checkstyle - path: target/checkstyle-result.xml - type: static-code-analysis - - name: pmd - path: target/pmd.xml - type: static-code-analysis - - name: pmd_cpd - path: target/cpd.xml - type: static-code-analysis - - name: testwiseCoverageReport - path: build/reports/testwise-coverage/tiaTests/tiaTests.json - type: testwise-coverage - - name: junit_**/test-results/test/*.xml - path: '**/test-results/test/*.xml' - type: junit diff --git a/src/main/resources/templates/aeolus/java/plain_maven_static_coverage.sh b/src/main/resources/templates/aeolus/java/plain_maven_static_coverage.sh deleted file mode 100644 index d74afc686999..000000000000 --- a/src/main/resources/templates/aeolus/java/plain_maven_static_coverage.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env bash -set -e -export AEOLUS_INITIAL_DIRECTORY=${PWD} -maven () { - echo '⚙️ executing maven' - mvn clean test -Pcoverage -} - -move_report_file () { - echo '⚙️ executing move_report_file' - mv target/tia/reports/*/testwise-coverage-*.json target/tia/reports/tiaTests.json -} - -maven_1 () { - echo '⚙️ executing maven_1' - mvn spotbugs:spotbugs checkstyle:checkstyle pmd:pmd pmd:cpd -} - -final_aeolus_post_action () { - set +e # from now on, we don't exit on errors - echo '⚙️ executing final_aeolus_post_action' - cd "${AEOLUS_INITIAL_DIRECTORY}" - maven_1 -} - -main () { - if [[ "${1}" == "aeolus_sourcing" ]]; then - return 0 # just source to use the methods in the subshell, no execution - fi - local _script_name - _script_name=${BASH_SOURCE[0]:-$0} - trap final_aeolus_post_action EXIT - - cd "${AEOLUS_INITIAL_DIRECTORY}" - bash -c "source ${_script_name} aeolus_sourcing; maven" - cd "${AEOLUS_INITIAL_DIRECTORY}" - bash -c "source ${_script_name} aeolus_sourcing; move_report_file" -} - -main "${@}" diff --git a/src/main/resources/templates/aeolus/java/plain_maven_static_coverage.yaml b/src/main/resources/templates/aeolus/java/plain_maven_static_coverage.yaml deleted file mode 100644 index 5017a23d8537..000000000000 --- a/src/main/resources/templates/aeolus/java/plain_maven_static_coverage.yaml +++ /dev/null @@ -1,30 +0,0 @@ -api: v0.0.1 -actions: - - name: maven - script: mvn clean test -Pcoverage - runAlways: false - - name: move_report_file - script: mv target/tia/reports/*/testwise-coverage-*.json target/tia/reports/tiaTests.json - runAlways: false - - name: maven - script: mvn spotbugs:spotbugs checkstyle:checkstyle pmd:pmd pmd:cpd - runAlways: true - results: - - name: spotbugs - path: target/spotbugsXml.xml - type: static-code-analysis - - name: checkstyle - path: target/checkstyle-result.xml - type: static-code-analysis - - name: pmd - path: target/pmd.xml - type: static-code-analysis - - name: pmd_cpd - path: target/cpd.xml - type: static-code-analysis - - name: testwiseCoverageReport - path: target/tia/reports/tiaTests.json - type: testwise-coverage - - name: junit - path: '**/target/surefire-reports/*.xml' - type: junit diff --git a/src/main/resources/templates/aeolus/kotlin/default_coverage.sh b/src/main/resources/templates/aeolus/kotlin/default_coverage.sh deleted file mode 100644 index 1840f600e00c..000000000000 --- a/src/main/resources/templates/aeolus/kotlin/default_coverage.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env bash -set -e -export AEOLUS_INITIAL_DIRECTORY=${PWD} -maven () { - echo '⚙️ executing maven' - mvn clean test -Pcoverage -} - -move_report_file () { - echo '⚙️ executing move_report_file' - mv target/tia/reports/*/testwise-coverage-*.json target/tia/reports/tiaTests.json -} - -main () { - if [[ "${1}" == "aeolus_sourcing" ]]; then - return 0 # just source to use the methods in the subshell, no execution - fi - local _script_name - _script_name=${BASH_SOURCE[0]:-$0} - cd "${AEOLUS_INITIAL_DIRECTORY}" - bash -c "source ${_script_name} aeolus_sourcing; maven" - cd "${AEOLUS_INITIAL_DIRECTORY}" - bash -c "source ${_script_name} aeolus_sourcing; move_report_file" -} - -main "${@}" diff --git a/src/main/resources/templates/aeolus/kotlin/default_coverage.yaml b/src/main/resources/templates/aeolus/kotlin/default_coverage.yaml deleted file mode 100644 index 5f2c972b6223..000000000000 --- a/src/main/resources/templates/aeolus/kotlin/default_coverage.yaml +++ /dev/null @@ -1,15 +0,0 @@ -api: v0.0.1 -actions: - - name: maven - script: mvn clean test -Pcoverage - runAlways: false - - name: move_report_file - script: mv target/tia/reports/*/testwise-coverage-*.json target/tia/reports/tiaTests.json - runAlways: false - results: - - name: testwiseCoverageReport - path: target/tia/reports/tiaTests.json - before: false - - name: junit - path: '**/target/surefire-reports/*.xml' - type: junit diff --git a/src/main/resources/templates/iris/hestia.hbs b/src/main/resources/templates/iris/hestia.hbs deleted file mode 100644 index 615534e452a7..000000000000 --- a/src/main/resources/templates/iris/hestia.hbs +++ /dev/null @@ -1,80 +0,0 @@ -{{#system~}}# Your task -You are an assistant to generate long and short descriptions for code hints. -Code hints are hints for programming exercises. -Code hints contain a part of the sample solution code that is needed to solve the exercise. -Code hints contain a short description of the code hint, which describes the purpose of the code hint. -Code hints contain a long description of the code hint, which describes the solution of the hint. -Code hints are made available to the student automatically if they have trouble solving the exercise. -The short description of the code hint is displayed to the student as soon as it is available. -The long description and solution code of the code hint is displayed to the student if they decide to use the code hint. -Your task is to generate the short and long description of a code hint. - -# Context -This is the context of the exercise and code hint that you can use to generate the short and long description. -## Programming exercise -Information about the programming exercise. -### Title -{{exercise.title}} -### Problem statement -```markdown -{{exercise.problemStatement}} -``` -## Code hint -Information about the code hint. -### Title -{{codeHint.title}} -### Solution code -{{#each codeHint.solutionEntries}} -#### {{this.filePath}} -{{#if (contains this "previousLine")}} -**Previous line:** {{this.previousLine}} -**Previous code:** -``` -{{this.previousCode}} -``` -{{/if}} -**New Line:** {{this.line}} -**New code:** -``` -{{this.code}} -``` -{{~/each}} - -## Previous attempts -These are the previous attempts at creating descriptions for this code hint. -The instructor did not like the previous attempts and wants you to generate new and different descriptions. -{{~/system}} -{{#if (contains session "messages")}} - {{#each session.messages}} - {{#if (equal this.sender "LLM")}} - {{#if (equal this.content[0].type "json")}} - {{#assistant~}} - **Short description:** - {{this.content[0].attributes.shortDescription}} - **Long description:** - {{this.content[0].attributes.longDescription}} - {{~/assistant}} - {{/if}} - {{#if (equal this.content[0].type "text")}} - {{#assistant~}} - {{this.content[0].textContent}} - {{~/assistant}} - {{/if}} - {{/if}} - {{~/each}} -{{/if}} -{{#system~}} - -# Your solution -Create your solution here. You can use any markdown you want -## Short description -{{~/system}} -{{#assistant~}} -{{gen 'shortDescription' temperature=0.2 max_tokens=100 stop="\\n"}} -{{~/assistant}} -{{#system~}} -## Long description -{{~/system}} -{{#assistant~}} -{{gen 'longDescription' temperature=0.2 max_tokens=1000}} -{{~/assistant}} diff --git a/src/main/resources/templates/java/maven_maven/test/projectTemplate/pom.xml b/src/main/resources/templates/java/maven_maven/test/projectTemplate/pom.xml index 997728be058b..93720b6f3aee 100644 --- a/src/main/resources/templates/java/maven_maven/test/projectTemplate/pom.xml +++ b/src/main/resources/templates/java/maven_maven/test/projectTemplate/pom.xml @@ -88,14 +88,6 @@ ${project.build.outputDirectory}/sun/ ${project.build.outputDirectory}/org/gradle/ ${project.build.outputDirectory}/worker/org/gradle/ - - ${project.build.outputDirectory}/com/squareup/ - ${project.build.outputDirectory}/com/teamscale/ - ${project.build.outputDirectory}/okhttp3/ - ${project.build.outputDirectory}/okio/ - ${project.build.outputDirectory}/retrofit2/ - ${project.build.outputDirectory}/shadow/ - @@ -180,54 +172,4 @@ - - - - coverage - - - com.teamscale - impacted-test-engine - 33.1.2 - - - - - - org.apache.maven.plugins - maven-surefire-plugin - 3.2.5 - - - okhttp3,com.teamscale,retrofit2,shadow,com.squareup,okio - - - - - com.teamscale - teamscale-maven-plugin - 33.1.2 - - http://localhost - dummy - ${exerciseNamePomXml}-Tests - dummy - testwise - - *${packageName}* - - - - - - prepare-tia-unit-test - - - - - - - - - diff --git a/src/main/resources/templates/java/test/gradle/projectTemplate/build.gradle b/src/main/resources/templates/java/test/gradle/projectTemplate/build.gradle index 97be1ab67bfd..611ce8468738 100644 --- a/src/main/resources/templates/java/test/gradle/projectTemplate/build.gradle +++ b/src/main/resources/templates/java/test/gradle/projectTemplate/build.gradle @@ -4,9 +4,7 @@ plugins { id 'pmd' id 'com.github.spotbugs' version '6.0.9' // %static-code-analysis-stop% - // %record-testwise-coverage-start% id 'com.teamscale' version '33.1.2' - // %record-testwise-coverage-stop% } apply plugin: 'java' @@ -97,14 +95,6 @@ def forbiddenPackageFolders = [ //(2) "$studentOutputDir/org/opentest4j/", "$studentOutputDir/sun/", "$studentOutputDir/worker/org/gradle/" - // %record-testwise-coverage-start% - ,"$studentOutputDir/com/teamscale/", - "$studentOutputDir/okhttp3/", - "$studentOutputDir/retrofit2/", - "$studentOutputDir/shadow/", - "$studentOutputDir/com/squareup/", - "$studentOutputDir/okio/" - // %record-testwise-coverage-stop% ] test { doFirst { //(1) @@ -125,17 +115,15 @@ configurations { behaviorTestRuntime.extendsFrom testRuntime } -task structuralTests(type: Test) { +tasks.register('structuralTests', Test) { testClassesDirs = sourceSets.structuralTest.output.classesDirs classpath += sourceSets.structuralTest.runtimeClasspath - useJUnitPlatform() } -task behaviorTests(type: Test) { +tasks.register('behaviorTests', Test) { testClassesDirs = sourceSets.behaviorTest.output.classesDirs classpath += sourceSets.behaviorTest.runtimeClasspath - useJUnitPlatform() } // %sequential-stop% @@ -178,20 +166,3 @@ pmd { } } // %static-code-analysis-stop% - - -// %record-testwise-coverage-start% -tasks.register('tiaTests', com.teamscale.TestImpacted) { - systemProperty "ares.security.trustedpackages", "okhttp3,com.teamscale,retrofit2,shadow,com.squareup,okio" - useJUnitPlatform() - filter { - excludeTestsMatching "AttributeTest" - excludeTestsMatching "ConstructorTest" - excludeTestsMatching "ClassTest" - excludeTestsMatching "MethodTest" - } - jacoco { - includes = ["${packageName}.*"] - } -} -// %record-testwise-coverage-stop% diff --git a/src/main/resources/templates/java/test/maven/projectTemplate/pom.xml b/src/main/resources/templates/java/test/maven/projectTemplate/pom.xml index c1a7d42fd3e8..24f9561bc6e9 100644 --- a/src/main/resources/templates/java/test/maven/projectTemplate/pom.xml +++ b/src/main/resources/templates/java/test/maven/projectTemplate/pom.xml @@ -83,14 +83,6 @@ ${project.build.outputDirectory}/sun/ ${project.build.outputDirectory}/org/gradle/ ${project.build.outputDirectory}/worker/org/gradle/ - - ${project.build.outputDirectory}/com/squareup/ - ${project.build.outputDirectory}/com/teamscale/ - ${project.build.outputDirectory}/okhttp3/ - ${project.build.outputDirectory}/okio/ - ${project.build.outputDirectory}/retrofit2/ - ${project.build.outputDirectory}/shadow/ - @@ -175,54 +167,4 @@ - - - - coverage - - - com.teamscale - impacted-test-engine - 33.1.2 - - - - - - org.apache.maven.plugins - maven-surefire-plugin - 3.2.5 - - - okhttp3,com.teamscale,retrofit2,shadow,com.squareup,okio - - - - - com.teamscale - teamscale-maven-plugin - 33.1.2 - - http://localhost - dummy - ${exerciseNamePomXml}-Tests - dummy - testwise - - *${packageName}* - - - - - - prepare-tia-unit-test - - - - - - - - - diff --git a/src/main/resources/templates/jenkins/java/gradle/regularRuns/pipeline.groovy b/src/main/resources/templates/jenkins/java/gradle/regularRuns/pipeline.groovy index b83202251c06..2478c6a8fe40 100644 --- a/src/main/resources/templates/jenkins/java/gradle/regularRuns/pipeline.groovy +++ b/src/main/resources/templates/jenkins/java/gradle/regularRuns/pipeline.groovy @@ -12,7 +12,6 @@ dockerImage = '#dockerImage' dockerFlags = '#dockerArgs' isSolutionBuild = "${env.JOB_NAME}" ==~ /.+-SOLUTION$/ -isTestWiseCoverageEnabled = #testWiseCoverage && isSolutionBuild isStaticCodeAnalysisEnabled = #isStaticCodeAnalysisEnabled /** @@ -37,11 +36,7 @@ private void runTestSteps() { */ void test() { stage('Test') { - if (isTestWiseCoverageEnabled) { - sh './gradlew clean test tiaTests --run-all-tests' - } else { - sh './gradlew clean test' - } + sh './gradlew clean test' } } @@ -66,25 +61,12 @@ private void staticCodeAnalysis() { } } -private void collectTestwiseCoverageReport() { - catchError { - sh ''' - rm -rf testwiseCoverageReport - mkdir testwiseCoverageReport - mv build/reports/testwise-coverage/tiaTests/tiaTests.json testwiseCoverageReport/ - ''' - } -} - /** * Script of the post build tasks aggregating all JUnit files in $WORKSPACE/results. * * Called by Jenkins. */ void postBuildTasks() { - if (isTestWiseCoverageEnabled) { - collectTestwiseCoverageReport() - } sh ''' rm -rf results mkdir results diff --git a/src/main/resources/templates/jenkins/java/maven/regularRuns/pipeline.groovy b/src/main/resources/templates/jenkins/java/maven/regularRuns/pipeline.groovy index cb76cbb4ee84..326b5897fbaa 100644 --- a/src/main/resources/templates/jenkins/java/maven/regularRuns/pipeline.groovy +++ b/src/main/resources/templates/jenkins/java/maven/regularRuns/pipeline.groovy @@ -12,7 +12,6 @@ dockerImage = '#dockerImage' dockerFlags = '#dockerArgs' isSolutionBuild = "${env.JOB_NAME}" ==~ /.+-SOLUTION$/ -isTestwiseCoverageEnabled = #testWiseCoverage && isSolutionBuild isStaticCodeAnalysisEnabled = #isStaticCodeAnalysisEnabled /** @@ -37,11 +36,7 @@ private void runTestSteps() { */ private void test() { stage('Test') { - if (isTestwiseCoverageEnabled) { - sh 'mvn clean test -B -Pcoverage' - } else { - sh 'mvn clean test -B' - } + sh 'mvn clean test -B' } } @@ -66,25 +61,12 @@ private void staticCodeAnalysis() { } } -private void collectTestwiseCoverageReport() { - catchError { - sh ''' - rm -rf testwiseCoverageReport - mkdir testwiseCoverageReport - mv target/tia/reports/*/*.json testwiseCoverageReport/ - ''' - } -} - /** * Script of the post build tasks aggregating all JUnit files in $WORKSPACE/results. * * Called by Jenkins. */ void postBuildTasks() { - if (isTestwiseCoverageEnabled) { - collectTestwiseCoverageReport() - } sh ''' rm -rf results mkdir results diff --git a/src/main/resources/templates/jenkins/kotlin/regularRuns/pipeline.groovy b/src/main/resources/templates/jenkins/kotlin/regularRuns/pipeline.groovy index cb76cbb4ee84..326b5897fbaa 100644 --- a/src/main/resources/templates/jenkins/kotlin/regularRuns/pipeline.groovy +++ b/src/main/resources/templates/jenkins/kotlin/regularRuns/pipeline.groovy @@ -12,7 +12,6 @@ dockerImage = '#dockerImage' dockerFlags = '#dockerArgs' isSolutionBuild = "${env.JOB_NAME}" ==~ /.+-SOLUTION$/ -isTestwiseCoverageEnabled = #testWiseCoverage && isSolutionBuild isStaticCodeAnalysisEnabled = #isStaticCodeAnalysisEnabled /** @@ -37,11 +36,7 @@ private void runTestSteps() { */ private void test() { stage('Test') { - if (isTestwiseCoverageEnabled) { - sh 'mvn clean test -B -Pcoverage' - } else { - sh 'mvn clean test -B' - } + sh 'mvn clean test -B' } } @@ -66,25 +61,12 @@ private void staticCodeAnalysis() { } } -private void collectTestwiseCoverageReport() { - catchError { - sh ''' - rm -rf testwiseCoverageReport - mkdir testwiseCoverageReport - mv target/tia/reports/*/*.json testwiseCoverageReport/ - ''' - } -} - /** * Script of the post build tasks aggregating all JUnit files in $WORKSPACE/results. * * Called by Jenkins. */ void postBuildTasks() { - if (isTestwiseCoverageEnabled) { - collectTestwiseCoverageReport() - } sh ''' rm -rf results mkdir results diff --git a/src/main/resources/templates/kotlin/test/maven/projectTemplate/pom.xml b/src/main/resources/templates/kotlin/test/maven/projectTemplate/pom.xml index e278d59eb3d2..c1c04e44339e 100644 --- a/src/main/resources/templates/kotlin/test/maven/projectTemplate/pom.xml +++ b/src/main/resources/templates/kotlin/test/maven/projectTemplate/pom.xml @@ -138,14 +138,6 @@ ${project.build.outputDirectory}/sun/ ${project.build.outputDirectory}/org/gradle/ ${project.build.outputDirectory}/worker/org/gradle/ - - ${project.build.outputDirectory}/com/squareup/ - ${project.build.outputDirectory}/com/teamscale/ - ${project.build.outputDirectory}/okhttp3/ - ${project.build.outputDirectory}/okio/ - ${project.build.outputDirectory}/retrofit2/ - ${project.build.outputDirectory}/shadow/ - @@ -153,54 +145,4 @@ - - - - coverage - - - com.teamscale - impacted-test-engine - ${teamscale.version} - - - - - - org.apache.maven.plugins - maven-surefire-plugin - 3.2.5 - - - okhttp3,com.teamscale,retrofit2,shadow,com.squareup,okio - - - - - com.teamscale - teamscale-maven-plugin - ${teamscale.version} - - http://localhost - dummy - ${exerciseNamePomXml}-Tests - dummy - testwise - - *${packageName}* - - - - - - prepare-tia-unit-test - - - - - - - - - diff --git a/src/main/webapp/app/assessment/assessment-shared.module.ts b/src/main/webapp/app/assessment/assessment-shared.module.ts index a2df1e03cc23..fe386487df8c 100644 --- a/src/main/webapp/app/assessment/assessment-shared.module.ts +++ b/src/main/webapp/app/assessment/assessment-shared.module.ts @@ -6,7 +6,6 @@ import { AssessmentComplaintAlertComponent } from './assessment-complaint-alert/ import { ScoreDisplayComponent } from '../shared/score-display/score-display.component'; import { UnreferencedFeedbackDetailComponent } from 'app/assessment/unreferenced-feedback-detail/unreferenced-feedback-detail.component'; import { ArtemisSharedComponentModule } from 'app/shared/components/shared-component.module'; -import { ArtemisComplaintsForTutorModule } from 'app/complaints/complaints-for-tutor/complaints-for-tutor.module'; import { AssessmentLocksComponent } from 'app/assessment/assessment-locks/assessment-locks.component'; import { RouterModule } from '@angular/router'; import { assessmentLocksRoute } from 'app/assessment/assessment-locks/assessment-locks.route'; @@ -18,13 +17,14 @@ import { ArtemisFeedbackModule } from 'app/exercises/shared/feedback/feedback.mo import { AssessmentNoteComponent } from 'app/assessment/assessment-note/assessment-note.component'; import { FeedbackContentPipe } from 'app/shared/pipes/feedback-content.pipe'; import { QuotePipe } from 'app/shared/pipes/quote.pipe'; +import { ComplaintsForTutorComponent } from 'app/complaints/complaints-for-tutor/complaints-for-tutor.component'; const ENTITY_STATES = [...assessmentLocksRoute]; @NgModule({ imports: [ ArtemisSharedModule, - ArtemisComplaintsForTutorModule, + ComplaintsForTutorComponent, ArtemisSharedComponentModule, RouterModule.forChild(ENTITY_STATES), ArtemisMarkdownModule, diff --git a/src/main/webapp/app/complaints/complaint-response.service.ts b/src/main/webapp/app/complaints/complaint-response.service.ts index 54e801ba687f..4ab4cb080f2e 100644 --- a/src/main/webapp/app/complaints/complaint-response.service.ts +++ b/src/main/webapp/app/complaints/complaint-response.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { HttpClient, HttpResponse } from '@angular/common/http'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; @@ -12,12 +12,10 @@ type EntityResponseType = HttpResponse; @Injectable({ providedIn: 'root' }) export class ComplaintResponseService { - private resourceUrl = 'api/complaints'; + private http = inject(HttpClient); + private accountService = inject(AccountService); - constructor( - private http: HttpClient, - private accountService: AccountService, - ) {} + private resourceUrl = 'api/complaints'; /** * Checks if a complaint response is locked for the currently logged-in user diff --git a/src/main/webapp/app/complaints/complaint.service.ts b/src/main/webapp/app/complaints/complaint.service.ts index 496cdaeacc6d..5ea2662dca9f 100644 --- a/src/main/webapp/app/complaints/complaint.service.ts +++ b/src/main/webapp/app/complaints/complaint.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { Injectable, inject } from '@angular/core'; import { HttpClient, HttpResponse } from '@angular/common/http'; import { Observable } from 'rxjs'; import dayjs from 'dayjs/esm'; @@ -31,12 +31,10 @@ export interface IComplaintService { @Injectable({ providedIn: 'root' }) export class ComplaintService implements IComplaintService { - private resourceUrl = 'api/complaints'; + private http = inject(HttpClient); + private complaintResponseService = inject(ComplaintResponseService); - constructor( - private http: HttpClient, - private complaintResponseService: ComplaintResponseService, - ) {} + private resourceUrl = 'api/complaints'; /** * Checks if a complaint is locked for the currently logged-in user diff --git a/src/main/webapp/app/complaints/complaints-for-students/complaints-student-view.component.ts b/src/main/webapp/app/complaints/complaints-for-students/complaints-student-view.component.ts index b469dcab432f..ed0bef126614 100644 --- a/src/main/webapp/app/complaints/complaints-for-students/complaints-student-view.component.ts +++ b/src/main/webapp/app/complaints/complaints-for-students/complaints-student-view.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, OnInit } from '@angular/core'; +import { Component, Input, OnInit, inject } from '@angular/core'; import { Exercise, getCourseFromExercise } from 'app/entities/exercise.model'; import { Complaint, ComplaintType } from 'app/entities/complaint.model'; import { ComplaintService } from 'app/complaints/complaint.service'; @@ -14,13 +14,26 @@ import dayjs from 'dayjs/esm'; import { HttpResponse } from '@angular/common/http'; import { faInfoCircle } from '@fortawesome/free-solid-svg-icons'; import { CourseManagementService } from 'app/course/manage/course-management.service'; +import { TranslateDirective } from 'app/shared/language/translate.directive'; +import { FaIconComponent } from '@fortawesome/angular-fontawesome'; +import { ComplaintsFormComponent } from 'app/complaints/form/complaints-form.component'; +import { ComplaintRequestComponent } from 'app/complaints/request/complaint-request.component'; +import { ComplaintResponseComponent } from 'app/complaints/response/complaint-response.component'; +import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; @Component({ selector: 'jhi-complaint-student-view', templateUrl: './complaints-student-view.component.html', styleUrls: ['../complaints.scss'], + standalone: true, + imports: [TranslateDirective, FaIconComponent, ComplaintsFormComponent, ComplaintRequestComponent, ComplaintResponseComponent, ArtemisTranslatePipe], }) export class ComplaintsStudentViewComponent implements OnInit { + private complaintService = inject(ComplaintService); + private serverDateService = inject(ArtemisServerDateService); + private accountService = inject(AccountService); + private courseService = inject(CourseManagementService); + @Input() exercise: Exercise; @Input() participation: StudentParticipation; @Input() result?: Result; @@ -46,13 +59,6 @@ export class ComplaintsStudentViewComponent implements OnInit { // Icons faInfoCircle = faInfoCircle; - constructor( - private complaintService: ComplaintService, - private serverDateService: ArtemisServerDateService, - private accountService: AccountService, - private courseService: CourseManagementService, - ) {} - /** * Loads the number of allowed complaints and feedback requests */ diff --git a/src/main/webapp/app/complaints/complaints-for-tutor/complaints-for-tutor.component.ts b/src/main/webapp/app/complaints/complaints-for-tutor/complaints-for-tutor.component.ts index 71c185e7d488..a98e34d60b17 100644 --- a/src/main/webapp/app/complaints/complaints-for-tutor/complaints-for-tutor.component.ts +++ b/src/main/webapp/app/complaints/complaints-for-tutor/complaints-for-tutor.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output, inject } from '@angular/core'; import { AlertService } from 'app/core/util/alert.service'; import { HttpErrorResponse } from '@angular/common/http'; import { ComplaintResponseService } from 'app/complaints/complaint-response.service'; @@ -13,15 +13,27 @@ import { Submission } from 'app/entities/submission.model'; import { isAllowedToRespondToComplaintAction } from 'app/assessment/assessment.service'; import { Course } from 'app/entities/course.model'; import { ComplaintAction, ComplaintResponseUpdateDTO } from 'app/entities/complaint-response-dto.model'; +import { TranslateDirective } from 'app/shared/language/translate.directive'; +import { FormsModule } from '@angular/forms'; +import { TextareaModule } from 'app/shared/textarea/textarea.module'; +import { ArtemisSharedCommonModule } from 'app/shared/shared-common.module'; +import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; export type AssessmentAfterComplaint = { complaintResponse: ComplaintResponse; onSuccess: () => void; onError: () => void }; @Component({ selector: 'jhi-complaints-for-tutor-form', templateUrl: './complaints-for-tutor.component.html', - providers: [], + standalone: true, + imports: [TranslateDirective, FormsModule, TextareaModule, ArtemisSharedCommonModule, ArtemisTranslatePipe], }) export class ComplaintsForTutorComponent implements OnInit { + private alertService = inject(AlertService); + private complaintResponseService = inject(ComplaintResponseService); + private activatedRoute = inject(ActivatedRoute); + private router = inject(Router); + private location = inject(Location); + @Input() complaint: Complaint; @Input() isTestRun = false; @Input() isAssessor = false; @@ -43,14 +55,6 @@ export class ComplaintsForTutorComponent implements OnInit { course?: Course; maxComplaintResponseTextLimit: number; - constructor( - private alertService: AlertService, - private complaintResponseService: ComplaintResponseService, - private activatedRoute: ActivatedRoute, - private router: Router, - private location: Location, - ) {} - ngOnInit(): void { this.course = getCourseFromExercise(this.exercise!); diff --git a/src/main/webapp/app/complaints/complaints-for-tutor/complaints-for-tutor.module.ts b/src/main/webapp/app/complaints/complaints-for-tutor/complaints-for-tutor.module.ts deleted file mode 100644 index 6b3e0eef93f4..000000000000 --- a/src/main/webapp/app/complaints/complaints-for-tutor/complaints-for-tutor.module.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { NgModule } from '@angular/core'; -import { ArtemisSharedModule } from 'app/shared/shared.module'; -import { ComplaintsForTutorComponent } from './complaints-for-tutor.component'; -import { ComplaintResponseService } from 'app/complaints/complaint-response.service'; -import { ComplaintService } from 'app/complaints/complaint.service'; -import { TextareaModule } from 'app/shared/textarea/textarea.module'; - -@NgModule({ - imports: [ArtemisSharedModule, TextareaModule], - declarations: [ComplaintsForTutorComponent], - exports: [ComplaintsForTutorComponent], - providers: [ComplaintService, ComplaintResponseService], -}) -export class ArtemisComplaintsForTutorModule {} diff --git a/src/main/webapp/app/complaints/complaints.module.ts b/src/main/webapp/app/complaints/complaints.module.ts index e851a892a538..709ea3322cc7 100644 --- a/src/main/webapp/app/complaints/complaints.module.ts +++ b/src/main/webapp/app/complaints/complaints.module.ts @@ -9,8 +9,7 @@ import { ComplaintResponseComponent } from 'app/complaints/response/complaint-re import { TextareaModule } from 'app/shared/textarea/textarea.module'; @NgModule({ - imports: [ArtemisSharedModule, TextareaModule], - declarations: [ComplaintsFormComponent, ComplaintsStudentViewComponent, ComplaintRequestComponent, ComplaintResponseComponent], + imports: [ArtemisSharedModule, TextareaModule, ComplaintsFormComponent, ComplaintsStudentViewComponent, ComplaintRequestComponent, ComplaintResponseComponent], exports: [ComplaintsStudentViewComponent], providers: [ComplaintService], }) diff --git a/src/main/webapp/app/complaints/form/complaints-form.component.ts b/src/main/webapp/app/complaints/form/complaints-form.component.ts index 015b05f95a95..5d5265a527e1 100644 --- a/src/main/webapp/app/complaints/form/complaints-form.component.ts +++ b/src/main/webapp/app/complaints/form/complaints-form.component.ts @@ -1,5 +1,5 @@ import { HttpErrorResponse } from '@angular/common/http'; -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output, inject } from '@angular/core'; import { ComplaintService } from 'app/complaints/complaint.service'; import { AlertService } from 'app/core/util/alert.service'; import { ComplaintType } from 'app/entities/complaint.model'; @@ -7,13 +7,22 @@ import { Course } from 'app/entities/course.model'; import { Exercise, getCourseFromExercise } from 'app/entities/exercise.model'; import { onError } from 'app/shared/util/global.utils'; import { ComplaintRequestDTO } from 'app/entities/complaint-request-dto.model'; +import { TranslateDirective } from 'app/shared/language/translate.directive'; +import { FormsModule } from '@angular/forms'; +import { TextareaModule } from 'app/shared/textarea/textarea.module'; +import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; @Component({ selector: 'jhi-complaint-form', templateUrl: './complaints-form.component.html', styleUrls: ['../complaints.scss'], + standalone: true, + imports: [TranslateDirective, FormsModule, TextareaModule, ArtemisTranslatePipe], }) export class ComplaintsFormComponent implements OnInit { + private complaintService = inject(ComplaintService); + private alertService = inject(AlertService); + @Input() exercise: Exercise; @Input() resultId: number; @Input() examId?: number; @@ -27,11 +36,6 @@ export class ComplaintsFormComponent implements OnInit { readonly ComplaintType = ComplaintType; - constructor( - private complaintService: ComplaintService, - private alertService: AlertService, - ) {} - ngOnInit(): void { this.course = getCourseFromExercise(this.exercise); this.maxComplaintTextLimit = this.course?.maxComplaintTextLimit ?? 0; diff --git a/src/main/webapp/app/complaints/list-of-complaints/list-of-complaints.component.ts b/src/main/webapp/app/complaints/list-of-complaints/list-of-complaints.component.ts index 131d552ffc4c..a2a3c2197a0b 100644 --- a/src/main/webapp/app/complaints/list-of-complaints/list-of-complaints.component.ts +++ b/src/main/webapp/app/complaints/list-of-complaints/list-of-complaints.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, inject } from '@angular/core'; import { AlertService } from 'app/core/util/alert.service'; import { ComplaintService } from 'app/complaints/complaint.service'; import { CourseManagementService } from 'app/course/manage/course-management.service'; @@ -8,20 +8,36 @@ import { ActivatedRoute, Router } from '@angular/router'; import { Course } from 'app/entities/course.model'; import { Observable, combineLatestWith } from 'rxjs'; import { StudentParticipation } from 'app/entities/participation/student-participation.model'; -import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { NgbModal, NgbTooltip } from '@ng-bootstrap/ng-bootstrap'; import { SortService } from 'app/shared/service/sort.service'; import { ArtemisDatePipe } from 'app/shared/pipes/artemis-date.pipe'; import { TranslateService } from '@ngx-translate/core'; import { onError } from 'app/shared/util/global.utils'; import { getLinkToSubmissionAssessment } from 'app/utils/navigation.utils'; import { faExclamationTriangle, faFolderOpen, faSort } from '@fortawesome/free-solid-svg-icons'; +import { TranslateDirective } from 'app/shared/language/translate.directive'; +import { FormsModule } from '@angular/forms'; +import { ArtemisSharedCommonModule } from 'app/shared/shared-common.module'; +import { FaIconComponent } from '@fortawesome/angular-fontawesome'; +import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; @Component({ selector: 'jhi-complaint-list', templateUrl: './list-of-complaints.component.html', - providers: [], + standalone: true, + imports: [TranslateDirective, FormsModule, ArtemisSharedCommonModule, FaIconComponent, NgbTooltip, ArtemisTranslatePipe], }) export class ListOfComplaintsComponent implements OnInit { + complaintService = inject(ComplaintService); + private alertService = inject(AlertService); + private route = inject(ActivatedRoute); + private router = inject(Router); + private modalService = inject(NgbModal); + private sortService = inject(SortService); + private translateService = inject(TranslateService); + private artemisDatePipe = inject(ArtemisDatePipe); + private courseManagementService = inject(CourseManagementService); + readonly ComplaintType = ComplaintType; public complaints: Complaint[] = []; @@ -49,18 +65,6 @@ export class ListOfComplaintsComponent implements OnInit { readonly FILTER_OPTION_ADDRESSED_COMPLAINTS = 4; // the number passed by the chart through the route indicating that only addressed complaints should be shown - constructor( - public complaintService: ComplaintService, - private alertService: AlertService, - private route: ActivatedRoute, - private router: Router, - private modalService: NgbModal, - private sortService: SortService, - private translateService: TranslateService, - private artemisDatePipe: ArtemisDatePipe, - private courseManagementService: CourseManagementService, - ) {} - ngOnInit(): void { this.route.params.pipe(combineLatestWith(this.route.queryParams, this.route.data)).subscribe((result) => { const params = result[0]; diff --git a/src/main/webapp/app/complaints/list-of-complaints/list-of-complaints.module.ts b/src/main/webapp/app/complaints/list-of-complaints/list-of-complaints.module.ts index df108f196b51..94122fae0136 100644 --- a/src/main/webapp/app/complaints/list-of-complaints/list-of-complaints.module.ts +++ b/src/main/webapp/app/complaints/list-of-complaints/list-of-complaints.module.ts @@ -3,13 +3,11 @@ import { ListOfComplaintsComponent } from './list-of-complaints.component'; import { ComplaintService } from 'app/complaints/complaint.service'; import { RouterModule } from '@angular/router'; import { listOfComplaintsRoute } from 'app/complaints/list-of-complaints/list-of-complaints.route'; -import { ArtemisSharedModule } from 'app/shared/shared.module'; const ENTITY_STATES = [...listOfComplaintsRoute]; @NgModule({ - imports: [ArtemisSharedModule, RouterModule.forChild(ENTITY_STATES)], - declarations: [ListOfComplaintsComponent], + imports: [RouterModule.forChild(ENTITY_STATES), ListOfComplaintsComponent], exports: [ListOfComplaintsComponent], providers: [ComplaintService], }) diff --git a/src/main/webapp/app/complaints/request/complaint-request.component.ts b/src/main/webapp/app/complaints/request/complaint-request.component.ts index ab9817fd3794..61065c172d4e 100644 --- a/src/main/webapp/app/complaints/request/complaint-request.component.ts +++ b/src/main/webapp/app/complaints/request/complaint-request.component.ts @@ -1,9 +1,16 @@ import { Component, Input } from '@angular/core'; import { Complaint, ComplaintType } from 'app/entities/complaint.model'; +import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap'; +import { TranslateDirective } from 'app/shared/language/translate.directive'; +import { FormsModule } from '@angular/forms'; +import { ArtemisSharedCommonModule } from 'app/shared/shared-common.module'; +import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; @Component({ selector: 'jhi-complaint-request', templateUrl: './complaint-request.component.html', + standalone: true, + imports: [NgbTooltip, TranslateDirective, FormsModule, ArtemisSharedCommonModule, ArtemisTranslatePipe], }) export class ComplaintRequestComponent { @Input() complaint: Complaint; diff --git a/src/main/webapp/app/complaints/response/complaint-response.component.ts b/src/main/webapp/app/complaints/response/complaint-response.component.ts index aabdeb3e3073..c2443cf7ea60 100644 --- a/src/main/webapp/app/complaints/response/complaint-response.component.ts +++ b/src/main/webapp/app/complaints/response/complaint-response.component.ts @@ -1,9 +1,15 @@ import { Component, Input } from '@angular/core'; import { Complaint, ComplaintType } from 'app/entities/complaint.model'; +import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap'; +import { FormsModule } from '@angular/forms'; +import { ArtemisSharedCommonModule } from 'app/shared/shared-common.module'; +import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; @Component({ selector: 'jhi-complaint-response', templateUrl: './complaint-response.component.html', + standalone: true, + imports: [NgbTooltip, FormsModule, ArtemisSharedCommonModule, ArtemisTranslatePipe], }) export class ComplaintResponseComponent { @Input() complaint: Complaint; diff --git a/src/main/webapp/app/course/dashboards/assessment-dashboard/assessment-dashboard-information.component.ts b/src/main/webapp/app/course/dashboards/assessment-dashboard/assessment-dashboard-information.component.ts index b2d0ef1ee8d3..36819ac62b0f 100644 --- a/src/main/webapp/app/course/dashboards/assessment-dashboard/assessment-dashboard-information.component.ts +++ b/src/main/webapp/app/course/dashboards/assessment-dashboard/assessment-dashboard-information.component.ts @@ -1,10 +1,14 @@ -import { Component, Input, OnChanges, OnDestroy, OnInit } from '@angular/core'; +import { Component, Input, OnChanges, OnDestroy, OnInit, inject } from '@angular/core'; import { DueDateStat } from 'app/course/dashboards/due-date-stat.model'; -import { LegendPosition } from '@swimlane/ngx-charts'; +import { LegendPosition, PieChartModule } from '@swimlane/ngx-charts'; import { TranslateService } from '@ngx-translate/core'; import { GraphColors } from 'app/entities/statistics.model'; import { Subscription } from 'rxjs'; import { Course } from 'app/entities/course.model'; +import { TranslateDirective } from 'app/shared/language/translate.directive'; +import { ArtemisSidePanelModule } from 'app/shared/side-panel/side-panel.module'; +import { RouterLink } from '@angular/router'; +import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; export class AssessmentDashboardInformationEntry { constructor( @@ -33,8 +37,12 @@ export class AssessmentDashboardInformationEntry { selector: 'jhi-assessment-dashboard-information', templateUrl: './assessment-dashboard-information.component.html', styleUrls: ['./assessment-dashboard-information.component.scss'], + standalone: true, + imports: [TranslateDirective, PieChartModule, ArtemisSidePanelModule, RouterLink, ArtemisTranslatePipe], }) export class AssessmentDashboardInformationComponent implements OnInit, OnChanges, OnDestroy { + private translateService = inject(TranslateService); + @Input() isExamMode: boolean; @Input() course: Course; @Input() examId?: number; @@ -71,8 +79,6 @@ export class AssessmentDashboardInformationComponent implements OnInit, OnChange themeSubscription: Subscription; - constructor(private translateService: TranslateService) {} - ngOnInit(): void { this.setup(); this.translateService.onLangChange.subscribe(() => { diff --git a/src/main/webapp/app/course/dashboards/assessment-dashboard/assessment-dashboard.component.ts b/src/main/webapp/app/course/dashboards/assessment-dashboard/assessment-dashboard.component.ts index a39b6af80e77..8ada9ad9321b 100644 --- a/src/main/webapp/app/course/dashboards/assessment-dashboard/assessment-dashboard.component.ts +++ b/src/main/webapp/app/course/dashboards/assessment-dashboard/assessment-dashboard.component.ts @@ -1,5 +1,5 @@ -import { Component, OnInit } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; +import { Component, OnInit, inject } from '@angular/core'; +import { ActivatedRoute, RouterLink } from '@angular/router'; import { CourseManagementService } from '../../manage/course-management.service'; import { AlertService } from 'app/core/util/alert.service'; import { User } from 'app/core/user/user.model'; @@ -17,19 +17,55 @@ import { Exam } from 'app/entities/exam/exam.model'; import { ExamManagementService } from 'app/exam/manage/exam-management.service'; import { ExerciseService } from 'app/exercises/shared/exercise/exercise.service'; import { QuizExercise } from 'app/entities/quiz/quiz-exercise.model'; -import { AssessmentDashboardInformationEntry } from './assessment-dashboard-information.component'; +import { AssessmentDashboardInformationComponent, AssessmentDashboardInformationEntry } from './assessment-dashboard-information.component'; import { TutorIssue, TutorIssueComplaintsChecker, TutorIssueRatingChecker, TutorIssueScoreChecker } from 'app/course/dashboards/assessment-dashboard/tutor-issue'; import { TutorLeaderboardElement } from 'app/shared/dashboards/tutor-leaderboard/tutor-leaderboard.model'; import { faClipboard, faHeartBroken, faSort, faTable } from '@fortawesome/free-solid-svg-icons'; import { DocumentationType } from 'app/shared/components/documentation-button/documentation-button.component'; +import { ArtemisSharedComponentModule } from 'app/shared/components/shared-component.module'; +import { FaIconComponent } from '@fortawesome/angular-fontawesome'; +import { TranslateDirective } from 'app/shared/language/translate.directive'; +import { ExamAssessmentButtonsComponent } from 'app/course/dashboards/assessment-dashboard/exam-assessment-buttons/exam-assessment-buttons.component'; +import { FormsModule } from '@angular/forms'; +import { ArtemisSharedCommonModule } from 'app/shared/shared-common.module'; +import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap'; +import { ArtemisTutorParticipationGraphModule } from 'app/shared/dashboards/tutor-participation-graph/tutor-participation-graph.module'; +import { ArtemisExerciseAssessmentDashboardModule } from 'app/exercises/shared/dashboards/tutor/exercise-assessment-dashboard.module'; +import { ArtemisTutorLeaderboardModule } from 'app/shared/dashboards/tutor-leaderboard/tutor-leaderboard.module'; +import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; @Component({ selector: 'jhi-assessment-dashboard', templateUrl: './assessment-dashboard.component.html', styleUrls: ['./exam-assessment-buttons/exam-assessment-buttons.component.scss'], providers: [CourseManagementService], + standalone: true, + imports: [ + ArtemisSharedComponentModule, + RouterLink, + FaIconComponent, + TranslateDirective, + ExamAssessmentButtonsComponent, + AssessmentDashboardInformationComponent, + FormsModule, + ArtemisSharedCommonModule, + NgbTooltip, + ArtemisTutorParticipationGraphModule, + ArtemisExerciseAssessmentDashboardModule, + ArtemisTutorLeaderboardModule, + ArtemisTranslatePipe, + ], }) export class AssessmentDashboardComponent implements OnInit { + private courseService = inject(CourseManagementService); + private exerciseService = inject(ExerciseService); + private examManagementService = inject(ExamManagementService); + private alertService = inject(AlertService); + private accountService = inject(AccountService); + private route = inject(ActivatedRoute); + private guidedTourService = inject(GuidedTourService); + private sortService = inject(SortService); + readonly TeamFilterProp = TeamFilterProp; readonly documentationType: DocumentationType = 'Assessment'; @@ -79,17 +115,6 @@ export class AssessmentDashboardComponent implements OnInit { faClipboard = faClipboard; faHeartBroken = faHeartBroken; - constructor( - private courseService: CourseManagementService, - private exerciseService: ExerciseService, - private examManagementService: ExamManagementService, - private alertService: AlertService, - private accountService: AccountService, - private route: ActivatedRoute, - private guidedTourService: GuidedTourService, - private sortService: SortService, - ) {} - /** * On init set the courseID, load all exercises and statistics for tutors and set the identity for the AccountService. */ diff --git a/src/main/webapp/app/course/dashboards/assessment-dashboard/assessment-dashboard.module.ts b/src/main/webapp/app/course/dashboards/assessment-dashboard/assessment-dashboard.module.ts index 1095436e3162..d4edadae0921 100644 --- a/src/main/webapp/app/course/dashboards/assessment-dashboard/assessment-dashboard.module.ts +++ b/src/main/webapp/app/course/dashboards/assessment-dashboard/assessment-dashboard.module.ts @@ -26,7 +26,9 @@ const ENTITY_STATES = [...assessmentDashboardRoute]; ArtemisExerciseAssessmentDashboardModule, ArtemisSharedComponentModule, PieChartModule, + AssessmentDashboardComponent, + AssessmentDashboardInformationComponent, + ExamAssessmentButtonsComponent, ], - declarations: [AssessmentDashboardComponent, AssessmentDashboardInformationComponent, ExamAssessmentButtonsComponent], }) export class ArtemisAssessmentDashboardModule {} diff --git a/src/main/webapp/app/course/dashboards/assessment-dashboard/exam-assessment-buttons/exam-assessment-buttons.component.ts b/src/main/webapp/app/course/dashboards/assessment-dashboard/exam-assessment-buttons/exam-assessment-buttons.component.ts index 7bc20e00e55c..de7010811fae 100644 --- a/src/main/webapp/app/course/dashboards/assessment-dashboard/exam-assessment-buttons/exam-assessment-buttons.component.ts +++ b/src/main/webapp/app/course/dashboards/assessment-dashboard/exam-assessment-buttons/exam-assessment-buttons.component.ts @@ -1,5 +1,5 @@ -import { Component, OnInit } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; +import { Component, OnInit, inject } from '@angular/core'; +import { ActivatedRoute, RouterLink } from '@angular/router'; import { StudentExamService } from 'app/exam/manage/student-exams/student-exam.service'; import { Subscription, forkJoin } from 'rxjs'; import { tap } from 'rxjs/operators'; @@ -15,12 +15,24 @@ import { AccountService } from 'app/core/auth/account.service'; import { onError } from 'app/shared/util/global.utils'; import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; import { faClipboard } from '@fortawesome/free-solid-svg-icons'; +import { FaIconComponent } from '@fortawesome/angular-fontawesome'; +import { TranslateDirective } from 'app/shared/language/translate.directive'; @Component({ selector: 'jhi-exam-assessment-buttons', templateUrl: './exam-assessment-buttons.component.html', + standalone: true, + imports: [RouterLink, FaIconComponent, TranslateDirective], }) export class ExamAssessmentButtonsComponent implements OnInit { + private route = inject(ActivatedRoute); + private examManagementService = inject(ExamManagementService); + private studentExamService = inject(StudentExamService); + private courseService = inject(CourseManagementService); + private alertService = inject(AlertService); + private accountService = inject(AccountService); + private artemisTranslatePipe = inject(ArtemisTranslatePipe); + courseId: number; examId: number; studentExams: StudentExam[]; @@ -38,16 +50,6 @@ export class ExamAssessmentButtonsComponent implements OnInit { // icons faClipboard = faClipboard; - constructor( - private route: ActivatedRoute, - private examManagementService: ExamManagementService, - private studentExamService: StudentExamService, - private courseService: CourseManagementService, - private alertService: AlertService, - private accountService: AccountService, - private artemisTranslatePipe: ArtemisTranslatePipe, - ) {} - /** * Initialize the courseId and examId */ diff --git a/src/main/webapp/app/course/learning-paths/components/competency-graph/competency-graph.component.html b/src/main/webapp/app/course/learning-paths/components/competency-graph/competency-graph.component.html index 99b808753650..630f93e343e8 100644 --- a/src/main/webapp/app/course/learning-paths/components/competency-graph/competency-graph.component.html +++ b/src/main/webapp/app/course/learning-paths/components/competency-graph/competency-graph.component.html @@ -1,6 +1,6 @@

(); readonly center$ = new Subject(); readonly zoomToFit$ = new Subject(); diff --git a/src/main/webapp/app/course/manage/course-management-tab-bar/course-management-tab-bar.component.ts b/src/main/webapp/app/course/manage/course-management-tab-bar/course-management-tab-bar.component.ts index e4b3865d3dbc..8e15c55fbd28 100644 --- a/src/main/webapp/app/course/manage/course-management-tab-bar/course-management-tab-bar.component.ts +++ b/src/main/webapp/app/course/manage/course-management-tab-bar/course-management-tab-bar.component.ts @@ -204,12 +204,6 @@ export class CourseManagementTabBarComponent implements OnInit, OnDestroy, After } private getExistingSummaryEntries(): EntitySummary { - const numberRepositories = - this.course?.exercises - ?.filter((exercise) => exercise.type === 'programming') - .map((exercise) => exercise?.numberOfParticipations ?? 0) - .reduce((repositorySum, numberOfParticipationsForRepository) => repositorySum + numberOfParticipationsForRepository, 0) ?? 0; - const numberOfExercisesPerType = new Map(); this.course?.exercises?.forEach((exercise) => { if (exercise.type === undefined) { @@ -219,8 +213,6 @@ export class CourseManagementTabBarComponent implements OnInit, OnDestroy, After numberOfExercisesPerType.set(exercise.type, oldValue + 1); }); - const numberExams = this.course?.numberOfExams ?? 0; - const numberLectures = this.course?.lectures?.length ?? 0; const numberStudents = this.course?.numberOfStudents ?? 0; const numberTutors = this.course?.numberOfTeachingAssistants ?? 0; const numberEditors = this.course?.numberOfEditors ?? 0; @@ -228,14 +220,6 @@ export class CourseManagementTabBarComponent implements OnInit, OnDestroy, After const isTestCourse = this.course?.testCourse; return { - 'artemisApp.course.delete.summary.numberRepositories': numberRepositories, - 'artemisApp.course.delete.summary.numberProgrammingExercises': numberOfExercisesPerType.get(ExerciseType.PROGRAMMING) ?? 0, - 'artemisApp.course.delete.summary.numberModelingExercises': numberOfExercisesPerType.get(ExerciseType.MODELING) ?? 0, - 'artemisApp.course.delete.summary.numberTextExercises': numberOfExercisesPerType.get(ExerciseType.TEXT) ?? 0, - 'artemisApp.course.delete.summary.numberFileUploadExercises': numberOfExercisesPerType.get(ExerciseType.FILE_UPLOAD) ?? 0, - 'artemisApp.course.delete.summary.numberQuizExercises': numberOfExercisesPerType.get(ExerciseType.QUIZ) ?? 0, - 'artemisApp.course.delete.summary.numberExams': numberExams, - 'artemisApp.course.delete.summary.numberLectures': numberLectures, 'artemisApp.course.delete.summary.numberStudents': numberStudents, 'artemisApp.course.delete.summary.numberTutors': numberTutors, 'artemisApp.course.delete.summary.numberEditors': numberEditors, @@ -259,6 +243,13 @@ export class CourseManagementTabBarComponent implements OnInit, OnDestroy, After return { ...this.getExistingSummaryEntries(), + 'artemisApp.course.delete.summary.numberExams': summary.numberExams, + 'artemisApp.course.delete.summary.numberLectures': summary.numberLectures, + 'artemisApp.course.delete.summary.numberProgrammingExercises': summary.numberProgrammingExercises, + 'artemisApp.course.delete.summary.numberTextExercises': summary.numberTextExercises, + 'artemisApp.course.delete.summary.numberFileUploadExercises': summary.numberFileUploadExercises, + 'artemisApp.course.delete.summary.numberQuizExercises': summary.numberQuizExercises, + 'artemisApp.course.delete.summary.numberModelingExercises': summary.numberModelingExercises, 'artemisApp.course.delete.summary.numberBuilds': summary.numberOfBuilds, 'artemisApp.course.delete.summary.numberCommunicationPosts': summary.numberOfCommunicationPosts, 'artemisApp.course.delete.summary.numberAnswerPosts': summary.numberOfAnswerPosts, diff --git a/src/main/webapp/app/course/manage/course-management.module.ts b/src/main/webapp/app/course/manage/course-management.module.ts index 12d386095e31..3aeca48c59d3 100644 --- a/src/main/webapp/app/course/manage/course-management.module.ts +++ b/src/main/webapp/app/course/manage/course-management.module.ts @@ -20,11 +20,9 @@ import { ArtemisLectureModule } from 'app/lecture/lecture.module'; import { ArtemisTextExerciseManagementModule } from 'app/exercises/text/manage/text-exercise-management.module'; import { ArtemisDashboardsModule } from 'app/shared/dashboards/dashboards.module'; import { ArtemisParticipationModule } from 'app/exercises/shared/participation/participation.module'; -import { ArtemisExerciseHintManagementModule } from 'app/exercises/shared/exercise-hint/manage/exercise-hint-management.module'; import { ArtemisModelingExerciseManagementModule } from 'app/exercises/modeling/manage/modeling-exercise-management.module'; import { ArtemisCourseScoresModule } from 'app/course/course-scores/course-scores.module'; import { ArtemisExerciseScoresModule } from 'app/exercises/shared/exercise-scores/exercise-scores.module'; -import { ArtemisComplaintsForTutorModule } from 'app/complaints/complaints-for-tutor/complaints-for-tutor.module'; import { ArtemisFileUploadAssessmentModule } from 'app/exercises/file-upload/assess/file-upload-assessment.module'; import { ArtemisModelingAssessmentEditorModule } from 'app/exercises/modeling/assess/modeling-assessment-editor/modeling-assessment-editor.module'; import { NgxDatatableModule } from '@siemens/ngx-datatable'; @@ -69,6 +67,7 @@ import { SubmissionResultStatusModule } from 'app/overview/submission-result-sta import { ImageCropperModalComponent } from 'app/course/manage/image-cropper-modal.component'; import { HeaderCourseComponent } from 'app/overview/header-course.component'; import { ArtemisMarkdownEditorModule } from 'app/shared/markdown-editor/markdown-editor.module'; +import { ComplaintsForTutorComponent } from 'app/complaints/complaints-for-tutor/complaints-for-tutor.component'; @NgModule({ imports: [ @@ -95,9 +94,8 @@ import { ArtemisMarkdownEditorModule } from 'app/shared/markdown-editor/markdown ArtemisModelingExerciseModule, ArtemisColorSelectorModule, ArtemisDashboardsModule, - ArtemisExerciseHintManagementModule, ArtemisParticipationModule, - ArtemisComplaintsForTutorModule, + ComplaintsForTutorComponent, ArtemisListOfComplaintsModule, ArtemisFileUploadAssessmentModule, ArtemisModelingAssessmentEditorModule, diff --git a/src/main/webapp/app/course/manage/course-update.component.ts b/src/main/webapp/app/course/manage/course-update.component.ts index fd7c49954ea4..8922497168dd 100644 --- a/src/main/webapp/app/course/manage/course-update.component.ts +++ b/src/main/webapp/app/course/manage/course-update.component.ts @@ -128,7 +128,7 @@ export class CourseUpdateComponent implements OnInit { this.course.maxComplaintResponseTextLimit! > 0; this.requestMoreFeedbackEnabled = this.course.maxRequestMoreFeedbackTimeDays! > 0; } else { - this.fileService.getTemplateCodeOfCondcut().subscribe({ + this.fileService.getTemplateCodeOfConduct().subscribe({ next: (res: HttpResponse) => { if (res.body) { this.course.courseInformationSharingMessagingCodeOfConduct = res.body; diff --git a/src/main/webapp/app/course/manage/detail/course-detail.component.ts b/src/main/webapp/app/course/manage/detail/course-detail.component.ts index c6a3e11211ba..4b5ecc80b629 100644 --- a/src/main/webapp/app/course/manage/detail/course-detail.component.ts +++ b/src/main/webapp/app/course/manage/detail/course-detail.component.ts @@ -222,12 +222,6 @@ export class CourseDetailComponent implements OnInit, OnDestroy { data: { course: this.course, disabled: !this.isAdmin, subSettingsType: IrisSubSettingsType.CHAT }, }); } - // TODO: Enable in future PR - // details.push({ - // type: DetailType.ProgrammingIrisEnabled, - // title: 'artemisApp.iris.settings.subSettings.enabled.hesita', - // data: { course: this.course, disabled: !this.isAdmin, subSettingsType: this.HESTIA }, - // }); return irisDetails; } diff --git a/src/main/webapp/app/detail-overview-list/components/programming-diff-report-detail/programming-diff-report-detail.component.ts b/src/main/webapp/app/detail-overview-list/components/programming-diff-report-detail/programming-diff-report-detail.component.ts index 94fb86848f4f..46999aa24a27 100644 --- a/src/main/webapp/app/detail-overview-list/components/programming-diff-report-detail/programming-diff-report-detail.component.ts +++ b/src/main/webapp/app/detail-overview-list/components/programming-diff-report-detail/programming-diff-report-detail.component.ts @@ -3,12 +3,12 @@ import type { ProgrammingDiffReportDetail } from 'app/detail-overview-list/detai import { FeatureToggle } from 'app/shared/feature-toggle/feature-toggle.service'; import { ButtonSize, ButtonType, TooltipPlacement } from 'app/shared/components/button.component'; import { faCodeCompare } from '@fortawesome/free-solid-svg-icons'; -import { ProgrammingExerciseGitDiffReport } from 'app/entities/hestia/programming-exercise-git-diff-report.model'; -import { GitDiffReportModalComponent } from 'app/exercises/programming/hestia/git-diff-report/git-diff-report-modal.component'; +import { ProgrammingExerciseGitDiffReport } from 'app/entities/programming-exercise-git-diff-report.model'; +import { GitDiffReportModalComponent } from 'app/exercises/programming/git-diff-report/git-diff-report-modal.component'; import { ArtemisSharedComponentModule } from 'app/shared/components/shared-component.module'; import { ArtemisSharedModule } from 'app/shared/shared.module'; import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; -import { GitDiffLineStatComponent } from 'app/exercises/programming/hestia/git-diff-report/git-diff-line-stat.component'; +import { GitDiffLineStatComponent } from 'app/exercises/programming/git-diff-report/git-diff-line-stat.component'; @Component({ selector: 'jhi-programming-diff-report-detail', diff --git a/src/main/webapp/app/detail-overview-list/detail.model.ts b/src/main/webapp/app/detail-overview-list/detail.model.ts index b34f408ef435..995f32024195 100644 --- a/src/main/webapp/app/detail-overview-list/detail.model.ts +++ b/src/main/webapp/app/detail-overview-list/detail.model.ts @@ -5,7 +5,7 @@ import { SolutionProgrammingExerciseParticipation } from 'app/entities/participa import { ProgrammingExerciseInstructorRepositoryType } from 'app/exercises/programming/manage/services/programming-exercise.service'; import { AuxiliaryRepository } from 'app/entities/programming/programming-exercise-auxiliary-repository-model'; import { ProgrammingExerciseParticipationType } from 'app/entities/programming/programming-exercise-participation.model'; -import { ProgrammingExerciseGitDiffReport } from 'app/entities/hestia/programming-exercise-git-diff-report.model'; +import { ProgrammingExerciseGitDiffReport } from 'app/entities/programming-exercise-git-diff-report.model'; import { BuildLogStatisticsDTO } from 'app/entities/programming/build-log-statistics-dto'; import { DetailType } from 'app/detail-overview-list/detail-overview-list.component'; import { SafeHtml } from '@angular/platform-browser'; diff --git a/src/main/webapp/app/entities/attachment.model.ts b/src/main/webapp/app/entities/attachment.model.ts index c070019513ee..78e6b639da8d 100644 --- a/src/main/webapp/app/entities/attachment.model.ts +++ b/src/main/webapp/app/entities/attachment.model.ts @@ -20,6 +20,4 @@ export class Attachment implements BaseEntity { lecture?: Lecture; exercise?: Exercise; attachmentUnit?: AttachmentUnit; - - constructor() {} } diff --git a/src/main/webapp/app/entities/course-deletion-summary.model.ts b/src/main/webapp/app/entities/course-deletion-summary.model.ts index 606a9d440d49..f36eadc241d4 100644 --- a/src/main/webapp/app/entities/course-deletion-summary.model.ts +++ b/src/main/webapp/app/entities/course-deletion-summary.model.ts @@ -2,4 +2,11 @@ export interface CourseDeletionSummaryDTO { numberOfBuilds: number; numberOfCommunicationPosts: number; numberOfAnswerPosts: number; + numberProgrammingExercises: number; + numberTextExercises: number; + numberFileUploadExercises: number; + numberQuizExercises: number; + numberModelingExercises: number; + numberExams: number; + numberLectures: number; } diff --git a/src/main/webapp/app/entities/hestia/code-hint-model.ts b/src/main/webapp/app/entities/hestia/code-hint-model.ts deleted file mode 100644 index 310a0a5839a2..000000000000 --- a/src/main/webapp/app/entities/hestia/code-hint-model.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ExerciseHint } from 'app/entities/hestia/exercise-hint.model'; -import { ProgrammingExerciseSolutionEntry } from 'app/entities/hestia/programming-exercise-solution-entry.model'; - -export enum CodeHintGenerationStep { - GIT_DIFF, - COVERAGE, - SOLUTION_ENTRIES, - CODE_HINTS, -} -export class CodeHint extends ExerciseHint { - public solutionEntries?: ProgrammingExerciseSolutionEntry[]; -} diff --git a/src/main/webapp/app/entities/hestia/coverage-file-report.model.ts b/src/main/webapp/app/entities/hestia/coverage-file-report.model.ts deleted file mode 100644 index 03c3b278b6e6..000000000000 --- a/src/main/webapp/app/entities/hestia/coverage-file-report.model.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { BaseEntity } from 'app/shared/model/base-entity'; -import { TestwiseCoverageReportEntry } from 'app/entities/hestia/testwise-coverage-report-entry.model'; - -export class CoverageFileReport implements BaseEntity { - public id?: number; - - public filePath?: string; - public lineCount?: number; - public coveredLineCount?: number; - public testwiseCoverageEntries?: TestwiseCoverageReportEntry[]; - - constructor() {} -} diff --git a/src/main/webapp/app/entities/hestia/coverage-report.model.ts b/src/main/webapp/app/entities/hestia/coverage-report.model.ts deleted file mode 100644 index c60906c135d0..000000000000 --- a/src/main/webapp/app/entities/hestia/coverage-report.model.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { BaseEntity } from 'app/shared/model/base-entity'; -import { CoverageFileReport } from 'app/entities/hestia/coverage-file-report.model'; - -export class CoverageReport implements BaseEntity { - public id?: number; - - public fileReports?: CoverageFileReport[]; - public coveredLineRatio?: number; - - constructor() {} -} diff --git a/src/main/webapp/app/entities/hestia/exercise-hint.model.ts b/src/main/webapp/app/entities/hestia/exercise-hint.model.ts deleted file mode 100644 index fa61b885b7ec..000000000000 --- a/src/main/webapp/app/entities/hestia/exercise-hint.model.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { ProgrammingExercise } from 'app/entities/programming/programming-exercise.model'; -import { BaseEntity } from 'app/shared/model/base-entity'; -import { ProgrammingExerciseServerSideTask } from 'app/entities/hestia/programming-exercise-task.model'; - -export enum HintType { - TEXT = 'text', - CODE = 'code', -} - -export class ExerciseHint implements BaseEntity { - public id?: number; - public title?: string; - public description?: string; - public content?: string; - public exercise?: ProgrammingExercise; - public type?: HintType; - public programmingExerciseTask?: ProgrammingExerciseServerSideTask; - public displayThreshold?: number; - public currentUserRating?: number; -} diff --git a/src/main/webapp/app/entities/hestia/programming-exercise-solution-entry.model.ts b/src/main/webapp/app/entities/hestia/programming-exercise-solution-entry.model.ts deleted file mode 100644 index 9380ec858a4f..000000000000 --- a/src/main/webapp/app/entities/hestia/programming-exercise-solution-entry.model.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { BaseEntity } from 'app/shared/model/base-entity'; -import { ProgrammingExerciseTestCase } from 'app/entities/programming/programming-exercise-test-case.model'; -import { CodeHint } from 'app/entities/hestia/code-hint-model'; - -export class ProgrammingExerciseSolutionEntry implements BaseEntity { - id?: number; - filePath?: string; - previousLine?: number; - line?: number; - previousCode?: string; - code?: string; - testCase?: ProgrammingExerciseTestCase; - codeHint?: CodeHint; -} diff --git a/src/main/webapp/app/entities/hestia/testwise-coverage-report-entry.model.ts b/src/main/webapp/app/entities/hestia/testwise-coverage-report-entry.model.ts deleted file mode 100644 index 59234fffbcfe..000000000000 --- a/src/main/webapp/app/entities/hestia/testwise-coverage-report-entry.model.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { BaseEntity } from 'app/shared/model/base-entity'; -import { ProgrammingExerciseTestCase } from 'app/entities/programming/programming-exercise-test-case.model'; - -export class TestwiseCoverageReportEntry implements BaseEntity { - public id?: number; - - public startLine?: number; - public lineCount?: number; - public testCase?: ProgrammingExerciseTestCase; - - constructor() {} -} diff --git a/src/main/webapp/app/entities/hestia/programming-exercise-git-diff-entry.model.ts b/src/main/webapp/app/entities/programming-exercise-git-diff-entry.model.ts similarity index 100% rename from src/main/webapp/app/entities/hestia/programming-exercise-git-diff-entry.model.ts rename to src/main/webapp/app/entities/programming-exercise-git-diff-entry.model.ts diff --git a/src/main/webapp/app/entities/hestia/programming-exercise-git-diff-report.model.ts b/src/main/webapp/app/entities/programming-exercise-git-diff-report.model.ts similarity index 85% rename from src/main/webapp/app/entities/hestia/programming-exercise-git-diff-report.model.ts rename to src/main/webapp/app/entities/programming-exercise-git-diff-report.model.ts index 5fccf4e2f4aa..d688911d1ccd 100644 --- a/src/main/webapp/app/entities/hestia/programming-exercise-git-diff-report.model.ts +++ b/src/main/webapp/app/entities/programming-exercise-git-diff-report.model.ts @@ -1,6 +1,6 @@ import { ProgrammingExercise } from 'app/entities/programming/programming-exercise.model'; import { BaseEntity } from 'app/shared/model/base-entity'; -import { ProgrammingExerciseGitDiffEntry } from 'app/entities/hestia/programming-exercise-git-diff-entry.model'; +import { ProgrammingExerciseGitDiffEntry } from 'app/entities/programming-exercise-git-diff-entry.model'; export class ProgrammingExerciseGitDiffReport implements BaseEntity { public id?: number; diff --git a/src/main/webapp/app/entities/hestia/programming-exercise-task.model.ts b/src/main/webapp/app/entities/programming-exercise-task.model.ts similarity index 100% rename from src/main/webapp/app/entities/hestia/programming-exercise-task.model.ts rename to src/main/webapp/app/entities/programming-exercise-task.model.ts diff --git a/src/main/webapp/app/entities/programming/build-config.model.ts b/src/main/webapp/app/entities/programming/build-config.model.ts index dcae29ec591f..af71acf37afe 100644 --- a/src/main/webapp/app/entities/programming/build-config.model.ts +++ b/src/main/webapp/app/entities/programming/build-config.model.ts @@ -6,6 +6,5 @@ export class BuildConfig { public projectType?: string; public scaEnabled?: boolean; public sequentialTestRunsEnabled?: boolean; - public testwiseCoverageEnabled?: boolean; public resultPaths?: string[]; } diff --git a/src/main/webapp/app/entities/programming/build-log.model.ts b/src/main/webapp/app/entities/programming/build-log.model.ts index fd4135a005cd..18e951c737a6 100644 --- a/src/main/webapp/app/entities/programming/build-log.model.ts +++ b/src/main/webapp/app/entities/programming/build-log.model.ts @@ -8,6 +8,11 @@ export enum BuildLogType { OTHER = 'OTHER', } +export type BuildLogLines = { + time: any; + logLines: string[]; +}; + export type BuildLogEntry = { time: any; log: string; diff --git a/src/main/webapp/app/entities/programming/programming-exercise-build.config.ts b/src/main/webapp/app/entities/programming/programming-exercise-build.config.ts index 55e5f5f487e2..d82e5f345c4d 100644 --- a/src/main/webapp/app/entities/programming/programming-exercise-build.config.ts +++ b/src/main/webapp/app/entities/programming/programming-exercise-build.config.ts @@ -11,11 +11,9 @@ export class ProgrammingExerciseBuildConfig { public timeoutSeconds?: number; public dockerFlags?: string; public windfile?: WindFile; - public testwiseCoverageEnabled?: boolean; public theiaImage?: string; constructor() { this.checkoutSolutionRepository = false; // default value - this.testwiseCoverageEnabled = false; // default value } } diff --git a/src/main/webapp/app/entities/programming/programming-exercise-test-case.model.ts b/src/main/webapp/app/entities/programming/programming-exercise-test-case.model.ts index 2d6822dc7ce4..5d43e28c7d16 100644 --- a/src/main/webapp/app/entities/programming/programming-exercise-test-case.model.ts +++ b/src/main/webapp/app/entities/programming/programming-exercise-test-case.model.ts @@ -1,6 +1,5 @@ import { ProgrammingExercise } from 'app/entities/programming/programming-exercise.model'; import { BaseEntity } from 'app/shared/model/base-entity'; -import { ProgrammingExerciseSolutionEntry } from 'app/entities/hestia/programming-exercise-solution-entry.model'; import { TestCaseStats } from './programming-exercise-test-case-statistics.model'; export enum Visibility { @@ -25,7 +24,6 @@ export class ProgrammingExerciseTestCase implements BaseEntity { visibility?: Visibility; exercise?: ProgrammingExercise; type?: ProgrammingExerciseTestCaseType; - solutionEntries?: ProgrammingExerciseSolutionEntry[]; // Utility information that is not actually part of the object stored on the server resultingPoints?: number; diff --git a/src/main/webapp/app/entities/programming/programming-exercise.model.ts b/src/main/webapp/app/entities/programming/programming-exercise.model.ts index 646f7bdfdf4d..bd9246f8569e 100644 --- a/src/main/webapp/app/entities/programming/programming-exercise.model.ts +++ b/src/main/webapp/app/entities/programming/programming-exercise.model.ts @@ -2,8 +2,7 @@ import { AssessmentType } from 'app/entities/assessment-type.model'; import { Course } from 'app/entities/course.model'; import { ExerciseGroup } from 'app/entities/exercise-group.model'; import { Exercise, ExerciseType, resetForImport } from 'app/entities/exercise.model'; -import { ExerciseHint } from 'app/entities/hestia/exercise-hint.model'; -import { ProgrammingExerciseGitDiffReport } from 'app/entities/hestia/programming-exercise-git-diff-report.model'; +import { ProgrammingExerciseGitDiffReport } from 'app/entities/programming-exercise-git-diff-report.model'; import { SolutionProgrammingExerciseParticipation } from 'app/entities/participation/solution-programming-exercise-participation.model'; import { TemplateProgrammingExerciseParticipation } from 'app/entities/participation/template-programming-exercise-participation.model'; import { BuildLogStatisticsDTO } from 'app/entities/programming/build-log-statistics-dto'; @@ -63,7 +62,6 @@ export class ProgrammingExercise extends Exercise { public showTestNamesToStudents?: boolean; public auxiliaryRepositories?: AuxiliaryRepository[]; public submissionPolicy?: SubmissionPolicy; - public exerciseHints?: ExerciseHint[]; public gitDiffReport?: ProgrammingExerciseGitDiffReport; public buildLogStatistics?: BuildLogStatisticsDTO; public buildConfig?: ProgrammingExerciseBuildConfig; @@ -74,11 +72,6 @@ export class ProgrammingExercise extends Exercise { public projectType?: ProjectType; - // helper attributes - - // this attribute is used to display the covered lines ratio - public coveredLinesRatio?: number; - /** * This attribute is used to generate a programming exercise with no connection to the VCS and CI. * This functionality is only for testing purposes. @@ -126,8 +119,6 @@ export function copyBuildConfigFromExerciseJson(exerciseJson: ProgrammingExercis buildConfig.timeoutSeconds = exerciseJson.timeoutSeconds ?? 0; buildConfig.windfile = exerciseJson.windfile ?? undefined; buildConfig.buildScript = exerciseJson.buildScript ?? ''; - buildConfig.testwiseCoverageEnabled = exerciseJson.testwiseCoverageEnabled ?? false; buildConfig.dockerFlags = exerciseJson.dockerFlags ?? ''; - return buildConfig; } diff --git a/src/main/webapp/app/exam/exam-scores/exam-scores-average-scores-graph.component.html b/src/main/webapp/app/exam/exam-scores/exam-scores-average-scores-graph.component.html index 35ab48c1854a..958122437f2b 100644 --- a/src/main/webapp/app/exam/exam-scores/exam-scores-average-scores-graph.component.html +++ b/src/main/webapp/app/exam/exam-scores/exam-scores-average-scores-graph.component.html @@ -1,6 +1,6 @@
-
+
(); + course = input.required(); courseId: number; examId: number; @@ -39,14 +49,6 @@ export class ExamScoresAverageScoresGraphComponent implements OnInit { xScaleMax = 100; lookup: NameToValueMap = {}; - constructor( - private navigationUtilService: ArtemisNavigationUtilService, - private activatedRoute: ActivatedRoute, - private service: StatisticsService, - private translateService: TranslateService, - private localeConversionService: LocaleConversionService, - ) {} - ngOnInit(): void { this.activatedRoute.params.subscribe((params) => { this.courseId = +params['courseId']; @@ -56,12 +58,12 @@ export class ExamScoresAverageScoresGraphComponent implements OnInit { } private initializeChart(): void { - this.lookup[this.averageScores.title] = { absoluteValue: this.averageScores.averagePoints! }; - const exerciseGroupAverage = this.averageScores.averagePercentage ? this.averageScores.averagePercentage : 0; - this.ngxData.push({ name: this.averageScores.title, value: exerciseGroupAverage }); + this.lookup[this.averageScores().title] = { absoluteValue: this.averageScores().averagePoints! }; + const exerciseGroupAverage = this.averageScores().averagePercentage ?? 0; + this.ngxData.push({ name: this.averageScores().title, value: exerciseGroupAverage }); this.ngxColor.domain.push(this.determineColor(true, exerciseGroupAverage)); this.xScaleMax = this.xScaleMax > exerciseGroupAverage ? this.xScaleMax : exerciseGroupAverage; - this.averageScores.exerciseResults.forEach((exercise) => { + this.averageScores().exerciseResults.forEach((exercise) => { const exerciseAverage = exercise.averagePercentage ?? 0; this.xScaleMax = this.xScaleMax > exerciseAverage ? this.xScaleMax : exerciseAverage; this.ngxData.push({ name: exercise.exerciseId + ' ' + exercise.title, value: exerciseAverage }); @@ -77,14 +79,14 @@ export class ExamScoresAverageScoresGraphComponent implements OnInit { } roundAndPerformLocalConversion(points: number | undefined) { - return this.localeConversionService.toLocaleString(roundValueSpecifiedByCourseSettings(points, this.course), this.course!.accuracyOfScores!); + return this.localeConversionService.toLocaleString(roundValueSpecifiedByCourseSettings(points, this.course()), this.course()!.accuracyOfScores!); } /** * We navigate to the exercise scores page when the user clicks on a data point */ navigateToExercise(exerciseId: number, exerciseType: ExerciseType) { - navigateToExamExercise(this.navigationUtilService, this.courseId, this.examId, this.averageScores.exerciseGroupId, exerciseType, exerciseId, 'scores'); + navigateToExamExercise(this.navigationUtilService, this.courseId, this.examId, this.averageScores().exerciseGroupId, exerciseType, exerciseId, 'scores'); } /** diff --git a/src/main/webapp/app/exam/exam-scores/exam-scores.component.html b/src/main/webapp/app/exam/exam-scores/exam-scores.component.html index 2fe2e07c3e3a..40b23cc91347 100644 --- a/src/main/webapp/app/exam/exam-scores/exam-scores.component.html +++ b/src/main/webapp/app/exam/exam-scores/exam-scores.component.html @@ -35,11 +35,11 @@

@if (examScoreDTO.maxPoints) {

{{ 'artemisApp.examScores.maxPoints' | artemisTranslate }}: {{ localize(examScoreDTO.maxPoints) }},
} - +
{{ exerciseGroups.length }} {{ 'artemisApp.examScores.noExerciseGroups' | artemisTranslate }}
,
- +
{{ aggregatedExamResults.noOfRegisteredUsers }} {{ 'artemisApp.examScores.registered' | artemisTranslate }}
diff --git a/src/main/webapp/app/exam/exam-scores/exam-scores.component.ts b/src/main/webapp/app/exam/exam-scores/exam-scores.component.ts index 94a6f375b3b7..97bb7fb19d39 100644 --- a/src/main/webapp/app/exam/exam-scores/exam-scores.component.ts +++ b/src/main/webapp/app/exam/exam-scores/exam-scores.component.ts @@ -1,8 +1,8 @@ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit, inject } from '@angular/core'; import { Subscription, forkJoin, of } from 'rxjs'; import { catchError } from 'rxjs/operators'; import { ExamManagementService } from 'app/exam/manage/exam-management.service'; -import { ActivatedRoute } from '@angular/router'; +import { ActivatedRoute, RouterLink } from '@angular/router'; import { SortService } from 'app/shared/service/sort.service'; import { download, generateCsv, mkConfig } from 'export-to-csv'; import { @@ -58,6 +58,11 @@ import { USERNAME_KEY, } from 'app/shared/export/export-constants'; import { BonusStrategy } from 'app/entities/bonus.model'; +import { ExamScoresAverageScoresGraphComponent } from 'app/exam/exam-scores/exam-scores-average-scores-graph.component'; +import { ArtemisParticipantScoresModule } from 'app/shared/participant-scores/participant-scores.module'; +import { ExportModule } from 'app/shared/export/export.module'; +import { ArtemisSharedCommonModule } from 'app/shared/shared-common.module'; +import { ArtemisSharedComponentModule } from 'app/shared/components/shared-component.module'; export enum MedianType { PASSED, @@ -70,8 +75,21 @@ export enum MedianType { templateUrl: './exam-scores.component.html', changeDetection: ChangeDetectionStrategy.OnPush, styleUrls: ['./exam-scores.component.scss', '../../shared/chart/vertical-bar-chart.scss'], + standalone: true, + imports: [RouterLink, ArtemisSharedComponentModule, ArtemisSharedCommonModule, ExamScoresAverageScoresGraphComponent, ArtemisParticipantScoresModule, ExportModule], }) export class ExamScoresComponent implements OnInit, OnDestroy { + private route = inject(ActivatedRoute); + private examService = inject(ExamManagementService); + private sortService = inject(SortService); + private alertService = inject(AlertService); + private changeDetector = inject(ChangeDetectorRef); + private languageHelper = inject(JhiLanguageHelper); + private localeConversionService = inject(LocaleConversionService); + private participantScoresService = inject(ParticipantScoresService); + private gradingSystemService = inject(GradingSystemService); + private courseManagementService = inject(CourseManagementService); + public examScoreDTO: ExamScoreDTO; public exerciseGroups: ExerciseGroup[]; public studentResults: StudentResult[]; @@ -130,18 +148,6 @@ export class ExamScoresComponent implements OnInit, OnDestroy { faExclamationTriangle = faExclamationTriangle; private languageChangeSubscription?: Subscription; - constructor( - private route: ActivatedRoute, - private examService: ExamManagementService, - private sortService: SortService, - private alertService: AlertService, - private changeDetector: ChangeDetectorRef, - private languageHelper: JhiLanguageHelper, - private localeConversionService: LocaleConversionService, - private participantScoresService: ParticipantScoresService, - private gradingSystemService: GradingSystemService, - private courseManagementService: CourseManagementService, - ) {} ngOnInit() { this.route.params.subscribe((params) => { @@ -676,7 +682,7 @@ export class ExamScoresComponent implements OnInit, OnDestroy { * Localizes a number, e.g. switching the decimal separator */ localize(numberToLocalize: number): string { - return this.localeConversionService.toLocaleString(numberToLocalize, this.course!.accuracyOfScores!); + return this.localeConversionService.toLocaleString(numberToLocalize, this.course?.accuracyOfScores); } /** diff --git a/src/main/webapp/app/exam/exam-scores/exam-scores.module.ts b/src/main/webapp/app/exam/exam-scores/exam-scores.module.ts deleted file mode 100644 index e439030cb776..000000000000 --- a/src/main/webapp/app/exam/exam-scores/exam-scores.module.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { NgModule } from '@angular/core'; -import { ExamScoresComponent } from './exam-scores.component'; -import { ArtemisSharedModule } from 'app/shared/shared.module'; -import { ArtemisExamScoresRoutingModule } from 'app/exam/exam-scores/exam-scores.route'; -import { ArtemisDataTableModule } from 'app/shared/data-table/data-table.module'; -import { NgxDatatableModule } from '@siemens/ngx-datatable'; -import { ArtemisResultModule } from 'app/exercises/shared/result/result.module'; -import { ArtemisSharedComponentModule } from 'app/shared/components/shared-component.module'; -import { ExamScoresAverageScoresGraphComponent } from 'app/exam/exam-scores/exam-scores-average-scores-graph.component'; -import { BarChartModule } from '@swimlane/ngx-charts'; -import { ArtemisParticipantScoresModule } from 'app/shared/participant-scores/participant-scores.module'; -import { ExportModule } from 'app/shared/export/export.module'; - -@NgModule({ - declarations: [ExamScoresComponent, ExamScoresAverageScoresGraphComponent], - imports: [ - ArtemisSharedModule, - ArtemisExamScoresRoutingModule, - ArtemisDataTableModule, - NgxDatatableModule, - ArtemisResultModule, - ArtemisSharedComponentModule, - BarChartModule, - ArtemisParticipantScoresModule, - ExportModule, - ], -}) -export class ArtemisExamScoresModule {} diff --git a/src/main/webapp/app/exam/exam-scores/exam-scores.route.ts b/src/main/webapp/app/exam/exam-scores/exam-scores.route.ts index 2635eae62e5f..c0b815477e42 100644 --- a/src/main/webapp/app/exam/exam-scores/exam-scores.route.ts +++ b/src/main/webapp/app/exam/exam-scores/exam-scores.route.ts @@ -1,13 +1,21 @@ -import { NgModule } from '@angular/core'; -import { RouterModule, Routes } from '@angular/router'; -import { UserRouteAccessService } from 'app/core/auth/user-route-access-service'; +import { Route, Routes } from '@angular/router'; import { ExamScoresComponent } from 'app/exam/exam-scores/exam-scores.component'; import { Authority } from 'app/shared/constants/authority.constants'; +import { UserRouteAccessService } from 'app/core/auth/user-route-access-service'; -const routes: Routes = [ +export const examScoresRoute: Route[] = [ { path: ':examId/scores', component: ExamScoresComponent, + }, +]; + +const EXAM_SCORES_ROUTES = [...examScoresRoute]; + +export const examScoresState: Routes = [ + { + path: '', + children: EXAM_SCORES_ROUTES, data: { authorities: [Authority.ADMIN, Authority.INSTRUCTOR], pageTitle: 'artemisApp.examScores.title', @@ -15,9 +23,3 @@ const routes: Routes = [ canActivate: [UserRouteAccessService], }, ]; - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule], -}) -export class ArtemisExamScoresRoutingModule {} diff --git a/src/main/webapp/app/exam/manage/exam-management.module.ts b/src/main/webapp/app/exam/manage/exam-management.module.ts index 42b34266ba2b..d3e3677195d2 100644 --- a/src/main/webapp/app/exam/manage/exam-management.module.ts +++ b/src/main/webapp/app/exam/manage/exam-management.module.ts @@ -1,6 +1,5 @@ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; -import { ArtemisExamScoresModule } from 'app/exam/exam-scores/exam-scores.module'; import { ExamManagementComponent } from 'app/exam/manage/exam-management.component'; import { examManagementState } from 'app/exam/manage/exam-management.route'; import { ExamUpdateComponent } from 'app/exam/manage/exams/exam-update.component'; @@ -67,17 +66,19 @@ import { ArtemisProgrammingExerciseModule } from 'app/exercises/programming/shar import { DetailModule } from 'app/detail-overview-list/detail.module'; import { ArtemisDurationFromSecondsPipe } from 'app/shared/pipes/artemis-duration-from-seconds.pipe'; import { NoDataComponent } from 'app/shared/no-data-component'; -import { GitDiffLineStatComponent } from 'app/exercises/programming/hestia/git-diff-report/git-diff-line-stat.component'; +import { SafeHtmlPipe } from 'app/shared/pipes/safe-html.pipe'; +import { GradeStepBoundsPipe } from 'app/shared/pipes/grade-step-bounds.pipe'; +import { examScoresState } from 'app/exam/exam-scores/exam-scores.route'; +import { GitDiffLineStatComponent } from 'app/exercises/programming/git-diff-report/git-diff-line-stat.component'; -const ENTITY_STATES = [...examManagementState]; +const ENTITY_STATES = [...examManagementState, ...examScoresState]; @NgModule({ // TODO: For better modularization we could define an exercise module with the corresponding exam routes - providers: [ArtemisDurationFromSecondsPipe], + providers: [ArtemisDurationFromSecondsPipe, SafeHtmlPipe, GradeStepBoundsPipe], imports: [ RouterModule.forChild(ENTITY_STATES), ArtemisTextExerciseModule, - ArtemisExamScoresModule, ArtemisSharedModule, FormDateTimePickerModule, ArtemisSharedComponentModule, @@ -109,6 +110,9 @@ const ENTITY_STATES = [...examManagementState]; DetailModule, NoDataComponent, GitDiffLineStatComponent, + SafeHtmlPipe, + GradeStepBoundsPipe, + BonusComponent, ], declarations: [ ExamManagementComponent, @@ -136,7 +140,6 @@ const ENTITY_STATES = [...examManagementState]; StudentExamDetailTableRowComponent, ExamImportComponent, ExamExerciseImportComponent, - BonusComponent, ExamEditWorkingTimeComponent, ExamEditWorkingTimeDialogComponent, ExamLiveAnnouncementCreateModalComponent, diff --git a/src/main/webapp/app/exam/manage/exams/exam-checklist-component/exam-checklist.component.html b/src/main/webapp/app/exam/manage/exams/exam-checklist-component/exam-checklist.component.html index d0525ddc1928..8e773545e824 100644 --- a/src/main/webapp/app/exam/manage/exams/exam-checklist-component/exam-checklist.component.html +++ b/src/main/webapp/app/exam/manage/exams/exam-checklist-component/exam-checklist.component.html @@ -50,7 +50,10 @@

  • - +
  • @@ -63,6 +66,7 @@

  • - +
  • - +
  • - +
  • @@ -139,7 +143,7 @@

  • - +
  • @@ -150,7 +154,7 @@

    + @@ -167,14 +171,14 @@

  • - +
  • - + @@ -191,14 +195,17 @@

    @if (examChecklist) {
  • - +
  • } - + @@ -461,7 +468,7 @@

  • - +
  • @if (exam.publishResultsDate) { @@ -487,7 +494,10 @@

    - +

    @if (isEvaluatingQuizExercises) { } @else { - + } @@ -536,7 +549,10 @@

    @if (isAssessingUnsubmittedExams) { } @else { - + } @@ -580,13 +596,13 @@

    • - +
    • - +
    • diff --git a/src/main/webapp/app/exam/manage/student-exams/student-exam-timeline/programming-exam-diff/programming-exercise-exam-diff.component.ts b/src/main/webapp/app/exam/manage/student-exams/student-exam-timeline/programming-exam-diff/programming-exercise-exam-diff.component.ts index 81a3f247a809..c3979797994f 100644 --- a/src/main/webapp/app/exam/manage/student-exams/student-exam-timeline/programming-exam-diff/programming-exercise-exam-diff.component.ts +++ b/src/main/webapp/app/exam/manage/student-exams/student-exam-timeline/programming-exam-diff/programming-exercise-exam-diff.component.ts @@ -3,7 +3,7 @@ import { ProgrammingExercise } from 'app/entities/programming/programming-exerci import { ProgrammingSubmission } from 'app/entities/programming/programming-submission.model'; import { FeatureToggle } from 'app/shared/feature-toggle/feature-toggle.service'; import { ButtonSize } from 'app/shared/components/button.component'; -import { GitDiffReportModalComponent } from 'app/exercises/programming/hestia/git-diff-report/git-diff-report-modal.component'; +import { GitDiffReportModalComponent } from 'app/exercises/programming/git-diff-report/git-diff-report-modal.component'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { ProgrammingExerciseService } from 'app/exercises/programming/manage/services/programming-exercise.service'; import { Exercise, IncludedInOverallScore } from 'app/entities/exercise.model'; @@ -11,7 +11,7 @@ import { ExamSubmissionComponent } from 'app/exam/participate/exercises/exam-sub import { Submission } from 'app/entities/submission.model'; import { ProgrammingExerciseStudentParticipation } from 'app/entities/participation/programming-exercise-student-participation.model'; import { faCodeCompare } from '@fortawesome/free-solid-svg-icons'; -import { ProgrammingExerciseGitDiffReport } from 'app/entities/hestia/programming-exercise-git-diff-report.model'; +import { ProgrammingExerciseGitDiffReport } from 'app/entities/programming-exercise-git-diff-report.model'; import { ExamPageComponent } from 'app/exam/participate/exercises/exam-page.component'; import { Observable, Subject, Subscription, debounceTime, take } from 'rxjs'; import { CachedRepositoryFilesService } from 'app/exercises/programming/manage/services/cached-repository-files.service'; diff --git a/src/main/webapp/app/exam/manage/student-exams/student-exam-timeline/student-exam-timeline.component.ts b/src/main/webapp/app/exam/manage/student-exams/student-exam-timeline/student-exam-timeline.component.ts index 70f4cfe76710..ac7f501a52d5 100644 --- a/src/main/webapp/app/exam/manage/student-exams/student-exam-timeline/student-exam-timeline.component.ts +++ b/src/main/webapp/app/exam/manage/student-exams/student-exam-timeline/student-exam-timeline.component.ts @@ -17,7 +17,7 @@ import { SubmissionVersionService } from 'app/exercises/shared/submission-versio import { ProgrammingExerciseExamDiffComponent } from 'app/exam/manage/student-exams/student-exam-timeline/programming-exam-diff/programming-exercise-exam-diff.component'; import { ProgrammingExerciseParticipationService } from 'app/exercises/programming/manage/services/programming-exercise-participation.service'; import { ExamPageComponent } from 'app/exam/participate/exercises/exam-page.component'; -import { ProgrammingExerciseGitDiffReport } from 'app/entities/hestia/programming-exercise-git-diff-report.model'; +import { ProgrammingExerciseGitDiffReport } from 'app/entities/programming-exercise-git-diff-report.model'; @Component({ selector: 'jhi-student-exam-timeline', diff --git a/src/main/webapp/app/exercises/programming/assess/programming-assessment.module.ts b/src/main/webapp/app/exercises/programming/assess/programming-assessment.module.ts index 944d2a220e60..7ae991b6ac22 100644 --- a/src/main/webapp/app/exercises/programming/assess/programming-assessment.module.ts +++ b/src/main/webapp/app/exercises/programming/assess/programming-assessment.module.ts @@ -5,7 +5,6 @@ import { FormDateTimePickerModule } from 'app/shared/date-time-picker/date-time- import { FormsModule } from '@angular/forms'; import { FeatureToggleModule } from 'app/shared/feature-toggle/feature-toggle.module'; import { ProgrammingAssessmentRepoExportButtonComponent } from 'app/exercises/programming/assess/repo-export/programming-assessment-repo-export-button.component'; -import { ArtemisComplaintsForTutorModule } from 'app/complaints/complaints-for-tutor/complaints-for-tutor.module'; import { ProgrammingAssessmentRepoExportDialogComponent } from 'app/exercises/programming/assess/repo-export/programming-assessment-repo-export-dialog.component'; import { ArtemisProgrammingAssessmentRoutingModule } from 'app/exercises/programming/assess/programming-assessment.route'; import { ArtemisAssessmentSharedModule } from 'app/assessment/assessment-shared.module'; @@ -18,6 +17,7 @@ import { AssessmentInstructionsModule } from 'app/assessment/assessment-instruct import { ArtemisHeaderExercisePageWithDetailsModule } from 'app/exercises/shared/exercise-headers/exercise-headers.module'; import { OrionTutorAssessmentComponent } from 'app/orion/assessment/orion-tutor-assessment.component'; import { SubmissionResultStatusModule } from 'app/overview/submission-result-status.module'; +import { ComplaintsForTutorComponent } from 'app/complaints/complaints-for-tutor/complaints-for-tutor.component'; @NgModule({ imports: [ @@ -26,7 +26,7 @@ import { SubmissionResultStatusModule } from 'app/overview/submission-result-sta FormDateTimePickerModule, FormsModule, FeatureToggleModule, - ArtemisComplaintsForTutorModule, + ComplaintsForTutorComponent, ArtemisProgrammingAssessmentRoutingModule, ArtemisAssessmentSharedModule, ArtemisCodeEditorModule, diff --git a/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-file-panel-title.component.html b/src/main/webapp/app/exercises/programming/git-diff-report/git-diff-file-panel-title.component.html similarity index 100% rename from src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-file-panel-title.component.html rename to src/main/webapp/app/exercises/programming/git-diff-report/git-diff-file-panel-title.component.html diff --git a/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-file-panel-title.component.scss b/src/main/webapp/app/exercises/programming/git-diff-report/git-diff-file-panel-title.component.scss similarity index 100% rename from src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-file-panel-title.component.scss rename to src/main/webapp/app/exercises/programming/git-diff-report/git-diff-file-panel-title.component.scss diff --git a/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-file-panel-title.component.ts b/src/main/webapp/app/exercises/programming/git-diff-report/git-diff-file-panel-title.component.ts similarity index 100% rename from src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-file-panel-title.component.ts rename to src/main/webapp/app/exercises/programming/git-diff-report/git-diff-file-panel-title.component.ts diff --git a/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-file-panel.component.html b/src/main/webapp/app/exercises/programming/git-diff-report/git-diff-file-panel.component.html similarity index 100% rename from src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-file-panel.component.html rename to src/main/webapp/app/exercises/programming/git-diff-report/git-diff-file-panel.component.html diff --git a/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-file-panel.component.scss b/src/main/webapp/app/exercises/programming/git-diff-report/git-diff-file-panel.component.scss similarity index 100% rename from src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-file-panel.component.scss rename to src/main/webapp/app/exercises/programming/git-diff-report/git-diff-file-panel.component.scss diff --git a/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-file-panel.component.ts b/src/main/webapp/app/exercises/programming/git-diff-report/git-diff-file-panel.component.ts similarity index 90% rename from src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-file-panel.component.ts rename to src/main/webapp/app/exercises/programming/git-diff-report/git-diff-file-panel.component.ts index 9e01edae26b9..0417e9239618 100644 --- a/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-file-panel.component.ts +++ b/src/main/webapp/app/exercises/programming/git-diff-report/git-diff-file-panel.component.ts @@ -1,9 +1,9 @@ import { ChangeDetectionStrategy, Component, ViewEncapsulation, computed, input, output } from '@angular/core'; -import { ProgrammingExerciseGitDiffEntry } from 'app/entities/hestia/programming-exercise-git-diff-entry.model'; +import { ProgrammingExerciseGitDiffEntry } from 'app/entities/programming-exercise-git-diff-entry.model'; import { faAngleDown, faAngleUp } from '@fortawesome/free-solid-svg-icons'; -import { GitDiffFilePanelTitleComponent } from 'app/exercises/programming/hestia/git-diff-report/git-diff-file-panel-title.component'; -import { GitDiffLineStatComponent } from 'app/exercises/programming/hestia/git-diff-report/git-diff-line-stat.component'; -import { GitDiffFileComponent } from 'app/exercises/programming/hestia/git-diff-report/git-diff-file.component'; +import { GitDiffFilePanelTitleComponent } from 'app/exercises/programming/git-diff-report/git-diff-file-panel-title.component'; +import { GitDiffLineStatComponent } from 'app/exercises/programming/git-diff-report/git-diff-line-stat.component'; +import { GitDiffFileComponent } from 'app/exercises/programming/git-diff-report/git-diff-file.component'; import { ArtemisSharedModule } from 'app/shared/shared.module'; @Component({ diff --git a/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-file.component.html b/src/main/webapp/app/exercises/programming/git-diff-report/git-diff-file.component.html similarity index 100% rename from src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-file.component.html rename to src/main/webapp/app/exercises/programming/git-diff-report/git-diff-file.component.html diff --git a/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-file.component.ts b/src/main/webapp/app/exercises/programming/git-diff-report/git-diff-file.component.ts similarity index 92% rename from src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-file.component.ts rename to src/main/webapp/app/exercises/programming/git-diff-report/git-diff-file.component.ts index 45456a115513..055875de5a6a 100644 --- a/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-file.component.ts +++ b/src/main/webapp/app/exercises/programming/git-diff-report/git-diff-file.component.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component, ViewEncapsulation, computed, effect, input, output, viewChild } from '@angular/core'; -import { ProgrammingExerciseGitDiffEntry } from 'app/entities/hestia/programming-exercise-git-diff-entry.model'; +import { ProgrammingExerciseGitDiffEntry } from 'app/entities/programming-exercise-git-diff-entry.model'; import { MonacoDiffEditorComponent } from 'app/shared/monaco-editor/monaco-diff-editor.component'; import { TranslateDirective } from 'app/shared/language/translate.directive'; diff --git a/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-line-stat.component.html b/src/main/webapp/app/exercises/programming/git-diff-report/git-diff-line-stat.component.html similarity index 100% rename from src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-line-stat.component.html rename to src/main/webapp/app/exercises/programming/git-diff-report/git-diff-line-stat.component.html diff --git a/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-line-stat.component.scss b/src/main/webapp/app/exercises/programming/git-diff-report/git-diff-line-stat.component.scss similarity index 100% rename from src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-line-stat.component.scss rename to src/main/webapp/app/exercises/programming/git-diff-report/git-diff-line-stat.component.scss diff --git a/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-line-stat.component.ts b/src/main/webapp/app/exercises/programming/git-diff-report/git-diff-line-stat.component.ts similarity index 100% rename from src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-line-stat.component.ts rename to src/main/webapp/app/exercises/programming/git-diff-report/git-diff-line-stat.component.ts diff --git a/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-report-modal.component.html b/src/main/webapp/app/exercises/programming/git-diff-report/git-diff-report-modal.component.html similarity index 100% rename from src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-report-modal.component.html rename to src/main/webapp/app/exercises/programming/git-diff-report/git-diff-report-modal.component.html diff --git a/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-report-modal.component.ts b/src/main/webapp/app/exercises/programming/git-diff-report/git-diff-report-modal.component.ts similarity index 97% rename from src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-report-modal.component.ts rename to src/main/webapp/app/exercises/programming/git-diff-report/git-diff-report-modal.component.ts index 7684573f821f..96a16d6986ee 100644 --- a/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-report-modal.component.ts +++ b/src/main/webapp/app/exercises/programming/git-diff-report/git-diff-report-modal.component.ts @@ -1,11 +1,11 @@ import { ChangeDetectionStrategy, Component, effect, inject, input, signal, untracked } from '@angular/core'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; -import { ProgrammingExerciseGitDiffReport } from 'app/entities/hestia/programming-exercise-git-diff-report.model'; +import { ProgrammingExerciseGitDiffReport } from 'app/entities/programming-exercise-git-diff-report.model'; import { ProgrammingExerciseService } from 'app/exercises/programming/manage/services/programming-exercise.service'; import { ProgrammingExerciseParticipationService } from 'app/exercises/programming/manage/services/programming-exercise-participation.service'; import { CachedRepositoryFilesService } from 'app/exercises/programming/manage/services/cached-repository-files.service'; import { firstValueFrom } from 'rxjs'; -import { GitDiffReportComponent } from 'app/exercises/programming/hestia/git-diff-report/git-diff-report.component'; +import { GitDiffReportComponent } from 'app/exercises/programming/git-diff-report/git-diff-report.component'; import { TranslateDirective } from 'app/shared/language/translate.directive'; @Component({ diff --git a/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-report.component.html b/src/main/webapp/app/exercises/programming/git-diff-report/git-diff-report.component.html similarity index 100% rename from src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-report.component.html rename to src/main/webapp/app/exercises/programming/git-diff-report/git-diff-report.component.html diff --git a/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-report.component.ts b/src/main/webapp/app/exercises/programming/git-diff-report/git-diff-report.component.ts similarity index 95% rename from src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-report.component.ts rename to src/main/webapp/app/exercises/programming/git-diff-report/git-diff-report.component.ts index cc916e5daf93..7edc4f32aaf3 100644 --- a/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-report.component.ts +++ b/src/main/webapp/app/exercises/programming/git-diff-report/git-diff-report.component.ts @@ -1,12 +1,12 @@ import { ChangeDetectionStrategy, Component, computed, effect, input, signal, untracked } from '@angular/core'; -import { ProgrammingExerciseGitDiffReport } from 'app/entities/hestia/programming-exercise-git-diff-report.model'; -import { ProgrammingExerciseGitDiffEntry } from 'app/entities/hestia/programming-exercise-git-diff-entry.model'; +import { ProgrammingExerciseGitDiffReport } from 'app/entities/programming-exercise-git-diff-report.model'; +import { ProgrammingExerciseGitDiffEntry } from 'app/entities/programming-exercise-git-diff-entry.model'; import { faSpinner, faTableColumns } from '@fortawesome/free-solid-svg-icons'; import { ButtonSize, ButtonType, TooltipPlacement } from 'app/shared/components/button.component'; -import { GitDiffLineStatComponent } from 'app/exercises/programming/hestia/git-diff-report/git-diff-line-stat.component'; +import { GitDiffLineStatComponent } from 'app/exercises/programming/git-diff-report/git-diff-line-stat.component'; import { ArtemisSharedComponentModule } from 'app/shared/components/shared-component.module'; import { ArtemisSharedModule } from 'app/shared/shared.module'; -import { GitDiffFilePanelComponent } from 'app/exercises/programming/hestia/git-diff-report/git-diff-file-panel.component'; +import { GitDiffFilePanelComponent } from 'app/exercises/programming/git-diff-report/git-diff-file-panel.component'; interface DiffInformation { path: string; diff --git a/src/main/webapp/app/exercises/programming/hestia/generation-overview/code-hint-generation-overview/code-hint-generation-overview.component.html b/src/main/webapp/app/exercises/programming/hestia/generation-overview/code-hint-generation-overview/code-hint-generation-overview.component.html deleted file mode 100644 index 9a62ddda158b..000000000000 --- a/src/main/webapp/app/exercises/programming/hestia/generation-overview/code-hint-generation-overview/code-hint-generation-overview.component.html +++ /dev/null @@ -1,52 +0,0 @@ -

      - -

      -
      -@if (isPerformedByStep?.size && currentStep !== undefined && exercise) { -
      -
      -
      - @if (allowBehavioralEntryGeneration ? currentStep !== 0 : currentStep !== 2) { - - } -
      -
      - -
      -
      - @if (!isNextStepAvailable()) { - - } - @if (currentStep !== 3) { - - } -
      -
      -
      - - @if (exercise) { - @if (allowBehavioralEntryGeneration) { - - } - @if (allowBehavioralEntryGeneration) { - - } - - - } -
      -} diff --git a/src/main/webapp/app/exercises/programming/hestia/generation-overview/code-hint-generation-overview/code-hint-generation-overview.component.scss b/src/main/webapp/app/exercises/programming/hestia/generation-overview/code-hint-generation-overview/code-hint-generation-overview.component.scss deleted file mode 100644 index 2fa141c0b0d4..000000000000 --- a/src/main/webapp/app/exercises/programming/hestia/generation-overview/code-hint-generation-overview/code-hint-generation-overview.component.scss +++ /dev/null @@ -1,49 +0,0 @@ -.button-row { - & > button { - margin-right: 5px; - } -} - -.status-wrapper { - flex-grow: 3; -} - -.butonene-wr .btn[disabled] { - pointer-events: all !important; -} - -.mat-expansion-panel.code-hint-creation-expansion-panel-wrapper, -.mat-expansion-panel.code-hint-creation-expansion-panel-wrapper-header { - .mat-expansion-panel-header { - padding: 10px 10px 10px 0; - border-bottom: 1px solid rgba(0, 0, 0, 0.25); - } - - .mat-expansion-panel-header-title, - .mat-expansion-panel-header-description { - flex-basis: 0; - } - - .mat-expansion-panel-header-description { - justify-content: space-between; - align-items: center; - } -} - -/* prevent resizing of opened expansion panels */ -.mat-expansion-panel-spacing { - margin: 0; -} - -.mat-expansion-panel.code-hint-creation-expansion-panel-wrapper-header { - font-weight: bold; - cursor: default; - - .mat-expansion-panel-header { - border-bottom: 2px solid; - } -} - -.sort-icon { - cursor: pointer; -} diff --git a/src/main/webapp/app/exercises/programming/hestia/generation-overview/code-hint-generation-overview/code-hint-generation-overview.component.ts b/src/main/webapp/app/exercises/programming/hestia/generation-overview/code-hint-generation-overview/code-hint-generation-overview.component.ts deleted file mode 100644 index d039db390a19..000000000000 --- a/src/main/webapp/app/exercises/programming/hestia/generation-overview/code-hint-generation-overview/code-hint-generation-overview.component.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import { ActivatedRoute, Router } from '@angular/router'; -import { ProgrammingExercise } from 'app/entities/programming/programming-exercise.model'; -import { CoverageReport } from 'app/entities/hestia/coverage-report.model'; -import { ProgrammingExerciseSolutionEntry } from 'app/entities/hestia/programming-exercise-solution-entry.model'; -import { CodeHint, CodeHintGenerationStep } from 'app/entities/hestia/code-hint-model'; -import { ProgrammingExerciseGitDiffReport } from 'app/entities/hestia/programming-exercise-git-diff-report.model'; - -@Component({ - selector: 'jhi-code-hint-generation-overview', - templateUrl: './code-hint-generation-overview.component.html', - styleUrls: ['./code-hint-generation-overview.component.scss'], -}) -export class CodeHintGenerationOverviewComponent implements OnInit { - exercise?: ProgrammingExercise; - - currentStep: CodeHintGenerationStep; - isPerformedByStep: Map; - selectedSolutionEntries?: ProgrammingExerciseSolutionEntry[]; - - allowBehavioralEntryGeneration = false; - - readonly GenerationStep = CodeHintGenerationStep; - - constructor( - private route: ActivatedRoute, - private router: Router, - ) {} - - ngOnInit() { - this.route.data.subscribe(({ exercise }) => { - this.exercise = exercise; - // set all steps to unperformed initially - this.isPerformedByStep = new Map(); - this.isPerformedByStep.set(CodeHintGenerationStep.SOLUTION_ENTRIES, false); - this.isPerformedByStep.set(CodeHintGenerationStep.CODE_HINTS, false); - if (exercise.buildConfig?.testwiseCoverageEnabled) { - this.currentStep = CodeHintGenerationStep.GIT_DIFF; - this.allowBehavioralEntryGeneration = true; - this.isPerformedByStep.set(CodeHintGenerationStep.GIT_DIFF, false); - this.isPerformedByStep.set(CodeHintGenerationStep.COVERAGE, false); - } else { - this.currentStep = CodeHintGenerationStep.SOLUTION_ENTRIES; - } - }); - } - - setLatestPerformedStep(latestUpdatedStep: CodeHintGenerationStep) { - if (this.currentStep >= latestUpdatedStep) { - return; - } - const optionalEntry = Array.from(this.isPerformedByStep.entries()) - .filter((a) => a[1]) - .sort((a, b) => b[0] - a[0]) - .first(); - this.currentStep = optionalEntry === undefined ? this.currentStep : optionalEntry![0]; - } - - isNextStepAvailable(): boolean { - return this.isPerformedByStep.get(this.currentStep) ?? false; - } - - onNextStep() { - this.currentStep = this.currentStep + 1; - } - - onPreviousStep() { - this.currentStep = this.currentStep - 1; - } - - onStepChange(step: CodeHintGenerationStep) { - this.currentStep = step; - } - - onDiffReportLoaded(diffReport?: ProgrammingExerciseGitDiffReport) { - this.isPerformedByStep.set(CodeHintGenerationStep.GIT_DIFF, diffReport !== undefined); - this.setLatestPerformedStep(CodeHintGenerationStep.GIT_DIFF); - } - - onCoverageReportLoaded(coverageReport?: CoverageReport) { - this.isPerformedByStep.set(CodeHintGenerationStep.COVERAGE, coverageReport !== undefined); - this.setLatestPerformedStep(CodeHintGenerationStep.COVERAGE); - } - - onSolutionEntryChanges(entries?: ProgrammingExerciseSolutionEntry[]) { - this.selectedSolutionEntries = entries; - this.isPerformedByStep.set(CodeHintGenerationStep.SOLUTION_ENTRIES, entries !== undefined && entries!.length > 0); - this.setLatestPerformedStep(CodeHintGenerationStep.SOLUTION_ENTRIES); - } - - onCodeHintsLoaded(codeHints?: CodeHint[]) { - this.isPerformedByStep.set(CodeHintGenerationStep.CODE_HINTS, codeHints !== undefined && codeHints!.length > 0); - this.setLatestPerformedStep(CodeHintGenerationStep.CODE_HINTS); - } -} diff --git a/src/main/webapp/app/exercises/programming/hestia/generation-overview/code-hint-generation-overview/code-hint-generation-overview.module.ts b/src/main/webapp/app/exercises/programming/hestia/generation-overview/code-hint-generation-overview/code-hint-generation-overview.module.ts deleted file mode 100644 index a9d7088b56c9..000000000000 --- a/src/main/webapp/app/exercises/programming/hestia/generation-overview/code-hint-generation-overview/code-hint-generation-overview.module.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { NgModule } from '@angular/core'; -import { ArtemisSharedModule } from 'app/shared/shared.module'; -import { CodeHintGenerationStatusComponent } from '../code-hint-generation-status/code-hint-generation-status.component'; -import { SolutionEntryDetailsModalComponent } from '../solution-entry-details-modal/solution-entry-details-modal.component'; -import { ArtemisExerciseHintSharedModule } from 'app/exercises/shared/exercise-hint/shared/exercise-hint-shared.module'; -import { ArtemisSharedComponentModule } from 'app/shared/components/shared-component.module'; -import { DiffGenerationStepComponent } from '../steps/diff-generation-step/diff-generation-step.component'; -import { CoverageGenerationStepComponent } from '../steps/coverage-generation-step/coverage-generation-step.component'; -import { SolutionEntryGenerationStepComponent } from '../steps/solution-entry-generation-step/solution-entry-generation-step.component'; -import { CodeHintGenerationStepComponent } from '../steps/code-hint-generation-step/code-hint-generation-step.component'; -import { TestwiseCoverageReportModule } from 'app/exercises/programming/hestia/testwise-coverage-report/testwise-coverage-report.module'; -import { CodeHintGenerationOverviewComponent } from 'app/exercises/programming/hestia/generation-overview/code-hint-generation-overview/code-hint-generation-overview.component'; -import { RouterModule } from '@angular/router'; -import { ArtemisMarkdownModule } from 'app/shared/markdown.module'; -import { MatExpansionModule } from '@angular/material/expansion'; -import { CodeHintGenerationStatusStepComponent } from 'app/exercises/programming/hestia/generation-overview/code-hint-generation-status/code-hint-generation-status-step.component'; -import { ManualSolutionEntryCreationModalComponent } from '../manual-solution-entry-creation-modal/manual-solution-entry-creation-modal.component'; -import { GitDiffReportComponent } from 'app/exercises/programming/hestia/git-diff-report/git-diff-report.component'; - -@NgModule({ - imports: [ - RouterModule, - ArtemisSharedModule, - ArtemisExerciseHintSharedModule, - ArtemisSharedComponentModule, - TestwiseCoverageReportModule, - ArtemisMarkdownModule, - MatExpansionModule, - GitDiffReportComponent, - ], - declarations: [ - CodeHintGenerationStatusComponent, - SolutionEntryDetailsModalComponent, - DiffGenerationStepComponent, - CoverageGenerationStepComponent, - SolutionEntryGenerationStepComponent, - CodeHintGenerationStepComponent, - CodeHintGenerationOverviewComponent, - CodeHintGenerationStatusStepComponent, - ManualSolutionEntryCreationModalComponent, - ], - exports: [ - CodeHintGenerationStatusComponent, - SolutionEntryDetailsModalComponent, - DiffGenerationStepComponent, - CoverageGenerationStepComponent, - SolutionEntryGenerationStepComponent, - CodeHintGenerationStepComponent, - CodeHintGenerationOverviewComponent, - CodeHintGenerationStatusStepComponent, - ], -}) -export class ArtemisCodeHintGenerationOverviewModule {} diff --git a/src/main/webapp/app/exercises/programming/hestia/generation-overview/code-hint-generation-status/code-hint-generation-status-step.component.html b/src/main/webapp/app/exercises/programming/hestia/generation-overview/code-hint-generation-status/code-hint-generation-status-step.component.html deleted file mode 100644 index 0fb688bdaade..000000000000 --- a/src/main/webapp/app/exercises/programming/hestia/generation-overview/code-hint-generation-status/code-hint-generation-status-step.component.html +++ /dev/null @@ -1,14 +0,0 @@ -
      -
      - @if (isPerformed) { - - } - @if (!isPerformed) { - - } -
      -
      - Current step - -
      -
      diff --git a/src/main/webapp/app/exercises/programming/hestia/generation-overview/code-hint-generation-status/code-hint-generation-status-step.component.ts b/src/main/webapp/app/exercises/programming/hestia/generation-overview/code-hint-generation-status/code-hint-generation-status-step.component.ts deleted file mode 100644 index d46904d04367..000000000000 --- a/src/main/webapp/app/exercises/programming/hestia/generation-overview/code-hint-generation-status/code-hint-generation-status-step.component.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Component, Input } from '@angular/core'; -import { faCheck, faDotCircle } from '@fortawesome/free-solid-svg-icons'; - -@Component({ - selector: 'jhi-code-hint-generation-status-step', - templateUrl: './code-hint-generation-status-step.component.html', - styleUrls: ['./code-hint-generation-status.component.scss'], -}) -export class CodeHintGenerationStatusStepComponent { - @Input() - currentlySelected: boolean; - - @Input() - isPerformed: boolean; - - @Input() - labelTranslationKey: string; - - @Input() - descriptionTranslationKey: string; - - faCheck = faCheck; - faDotCircle = faDotCircle; -} diff --git a/src/main/webapp/app/exercises/programming/hestia/generation-overview/code-hint-generation-status/code-hint-generation-status.component.html b/src/main/webapp/app/exercises/programming/hestia/generation-overview/code-hint-generation-status/code-hint-generation-status.component.html deleted file mode 100644 index 6cc22ad5429d..000000000000 --- a/src/main/webapp/app/exercises/programming/hestia/generation-overview/code-hint-generation-status/code-hint-generation-status.component.html +++ /dev/null @@ -1,45 +0,0 @@ -
      - - @if (isPerformedByStep.get(GenerationStep.GIT_DIFF) !== undefined) { - - } - @if (isPerformedByStep.get(GenerationStep.GIT_DIFF) !== undefined) { -
      - } - - @if (isPerformedByStep.get(GenerationStep.COVERAGE) !== undefined) { - - } - @if (isPerformedByStep.get(GenerationStep.COVERAGE) !== undefined) { -
      - } - - -
      - - -
      diff --git a/src/main/webapp/app/exercises/programming/hestia/generation-overview/code-hint-generation-status/code-hint-generation-status.component.scss b/src/main/webapp/app/exercises/programming/hestia/generation-overview/code-hint-generation-status/code-hint-generation-status.component.scss deleted file mode 100644 index 5a790263e647..000000000000 --- a/src/main/webapp/app/exercises/programming/hestia/generation-overview/code-hint-generation-status/code-hint-generation-status.component.scss +++ /dev/null @@ -1,65 +0,0 @@ -.code-hint-generation-wrapper { - display: flex; - justify-content: space-between; - - .planned, - .finished, - .check { - color: var(--success); - } - - .unset { - color: var(--secondary); - } - - .current { - color: var(--warning); - } - - li { - display: list-item !important; - } - - .connector { - width: 100%; - min-width: 50px; - border-top: 2px solid; - margin-top: 1rem; - } -} - -.status-step { - width: 25px; - overflow-x: visible; - justify-content: flex-start; - display: flex; - flex-direction: column; - align-items: center; - - .status-step-content { - min-width: fit-content; - white-space: nowrap; - - .selected-label { - font-weight: bold; - } - } - - .header-icon { - z-index: 2; - font-size: 2em; - - & > fa-icon { - cursor: pointer; - background-color: var(--programming-exercise-instruction-step-wizard-card-header-background); - width: 30px; - height: 30px; - text-align: center; - padding: 6px 0; - font-size: 12px; - line-height: 1.428571429; - border-radius: 15px; - border-color: var(--programming-exercise-instruction-step-wizard-btn-border-color); - } - } -} diff --git a/src/main/webapp/app/exercises/programming/hestia/generation-overview/code-hint-generation-status/code-hint-generation-status.component.ts b/src/main/webapp/app/exercises/programming/hestia/generation-overview/code-hint-generation-status/code-hint-generation-status.component.ts deleted file mode 100644 index b0c31d9f9676..000000000000 --- a/src/main/webapp/app/exercises/programming/hestia/generation-overview/code-hint-generation-status/code-hint-generation-status.component.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core'; -import { CodeHintGenerationStep } from 'app/entities/hestia/code-hint-model'; - -@Component({ - selector: 'jhi-code-hint-generation-status', - templateUrl: './code-hint-generation-status.component.html', - styleUrls: ['./code-hint-generation-status.component.scss'], -}) -export class CodeHintGenerationStatusComponent { - @Input() - currentStep: CodeHintGenerationStep; - - @Input() - isPerformedByStep: Map; - - @Output() - onStepChange = new EventEmitter(); - - readonly GenerationStep = CodeHintGenerationStep; - - constructor() {} - - onSelectStep(step: CodeHintGenerationStep) { - this.onStepChange.emit(step); - } -} diff --git a/src/main/webapp/app/exercises/programming/hestia/generation-overview/manual-solution-entry-creation-modal/manual-solution-entry-creation-modal.component.html b/src/main/webapp/app/exercises/programming/hestia/generation-overview/manual-solution-entry-creation-modal/manual-solution-entry-creation-modal.component.html deleted file mode 100644 index 842eb668da94..000000000000 --- a/src/main/webapp/app/exercises/programming/hestia/generation-overview/manual-solution-entry-creation-modal/manual-solution-entry-creation-modal.component.html +++ /dev/null @@ -1,43 +0,0 @@ -
      - - -
      diff --git a/src/main/webapp/app/exercises/programming/hestia/generation-overview/manual-solution-entry-creation-modal/manual-solution-entry-creation-modal.component.ts b/src/main/webapp/app/exercises/programming/hestia/generation-overview/manual-solution-entry-creation-modal/manual-solution-entry-creation-modal.component.ts deleted file mode 100644 index 0df03f1664b3..000000000000 --- a/src/main/webapp/app/exercises/programming/hestia/generation-overview/manual-solution-entry-creation-modal/manual-solution-entry-creation-modal.component.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { Component, EventEmitter, OnDestroy, OnInit, ViewChild } from '@angular/core'; -import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; -import { Subject } from 'rxjs'; -import { ProgrammingExerciseTestCase } from 'app/entities/programming/programming-exercise-test-case.model'; -import { ProgrammingExerciseSolutionEntry } from 'app/entities/hestia/programming-exercise-solution-entry.model'; -import { ProgrammingExerciseService } from 'app/exercises/programming/manage/services/programming-exercise.service'; -import { CodeHint } from 'app/entities/hestia/code-hint-model'; -import { ProgrammingExerciseSolutionEntryService } from 'app/exercises/shared/exercise-hint/services/programming-exercise-solution-entry.service'; -import { SolutionEntryComponent } from 'app/exercises/shared/exercise-hint/shared/solution-entry.component'; - -@Component({ - selector: 'jhi-manual-solution-entry-creation-modal', - templateUrl: './manual-solution-entry-creation-modal.component.html', -}) -export class ManualSolutionEntryCreationModalComponent implements OnInit, OnDestroy { - @ViewChild('solutionEntryComponent', { static: false }) solutionEntryComponent: SolutionEntryComponent; - - solutionEntry = new ProgrammingExerciseSolutionEntry(); - - exerciseId: number; - codeHint?: CodeHint; - testCases?: ProgrammingExerciseTestCase[]; - onEntryCreated = new EventEmitter(); - - solutionRepositoryFileNames?: string[]; - - private dialogErrorSource = new Subject(); - dialogError$ = this.dialogErrorSource.asObservable(); - - constructor( - private activeModal: NgbActiveModal, - private exerciseService: ProgrammingExerciseService, - private solutionEntryService: ProgrammingExerciseSolutionEntryService, - ) { - this.solutionEntry.code = ''; - this.solutionEntry.line = 1; - } - - ngOnDestroy(): void { - this.dialogErrorSource.unsubscribe(); - } - - ngOnInit(): void { - this.solutionEntry.codeHint = this.codeHint; - // only load test cases if not set as an input - if (!this.testCases) { - this.exerciseService.getAllTestCases(this.exerciseId).subscribe({ - next: (testCases) => { - this.testCases = testCases; - }, - error: (error) => this.dialogErrorSource.error(error.message), - }); - } - this.exerciseService.getSolutionFileNames(this.exerciseId).subscribe({ - next: (fileNames) => { - this.solutionRepositoryFileNames = fileNames; - }, - error: (error) => this.dialogErrorSource.error(error.message), - }); - } - - /** - * Updates the solution entry editor to refer to the correct file path. - * In particular, this updates the syntax highlighting of the editor. - */ - onUpdateFilePath(): void { - this.solutionEntryComponent.setupEditor(); - } - - clear() { - this.activeModal.close(); - } - - onCreateEntry() { - // eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain - this.solutionEntryService.createSolutionEntry(this.exerciseId, this.solutionEntry?.testCase?.id!, this.solutionEntry).subscribe({ - next: (createdEntry) => { - this.dialogErrorSource.next(''); - this.onEntryCreated.emit(createdEntry); - this.activeModal.close(); - }, - error: (error) => this.dialogErrorSource.error(error.message), - }); - } -} diff --git a/src/main/webapp/app/exercises/programming/hestia/generation-overview/solution-entry-details-modal/solution-entry-details-modal.component.html b/src/main/webapp/app/exercises/programming/hestia/generation-overview/solution-entry-details-modal/solution-entry-details-modal.component.html deleted file mode 100644 index 721b53f675b1..000000000000 --- a/src/main/webapp/app/exercises/programming/hestia/generation-overview/solution-entry-details-modal/solution-entry-details-modal.component.html +++ /dev/null @@ -1,21 +0,0 @@ -
      - - -
      diff --git a/src/main/webapp/app/exercises/programming/hestia/generation-overview/solution-entry-details-modal/solution-entry-details-modal.component.ts b/src/main/webapp/app/exercises/programming/hestia/generation-overview/solution-entry-details-modal/solution-entry-details-modal.component.ts deleted file mode 100644 index 8e2dcbacb3ab..000000000000 --- a/src/main/webapp/app/exercises/programming/hestia/generation-overview/solution-entry-details-modal/solution-entry-details-modal.component.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Component, OnDestroy } from '@angular/core'; -import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; -import { ProgrammingExerciseSolutionEntry } from 'app/entities/hestia/programming-exercise-solution-entry.model'; -import { Subject } from 'rxjs'; -import { ProgrammingExerciseSolutionEntryService } from 'app/exercises/shared/exercise-hint/services/programming-exercise-solution-entry.service'; - -@Component({ - selector: 'jhi-solution-entry-details-modal', - templateUrl: './solution-entry-details-modal.component.html', -}) -export class SolutionEntryDetailsModalComponent implements OnDestroy { - exerciseId: number; - solutionEntry: ProgrammingExerciseSolutionEntry; - isEditable: boolean; - - private dialogErrorSource = new Subject(); - dialogError$ = this.dialogErrorSource.asObservable(); - - constructor( - private activeModal: NgbActiveModal, - private solutionEntryService: ProgrammingExerciseSolutionEntryService, - ) {} - - ngOnDestroy(): void { - this.dialogErrorSource.unsubscribe(); - } - - clear() { - this.activeModal.close(); - } - - saveSolutionEntry() { - // eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain - this.solutionEntryService.updateSolutionEntry(this.exerciseId, this.solutionEntry.testCase?.id!, this.solutionEntry.id!, this.solutionEntry).subscribe({ - next: (updatedEntry) => { - this.solutionEntry = updatedEntry; - this.activeModal.close(); - }, - error: (error) => this.dialogErrorSource.error(error.message), - }); - } -} diff --git a/src/main/webapp/app/exercises/programming/hestia/generation-overview/steps/code-hint-generation-step/code-hint-generation-step.component.html b/src/main/webapp/app/exercises/programming/hestia/generation-overview/steps/code-hint-generation-step/code-hint-generation-step.component.html deleted file mode 100644 index 70538fa15b02..000000000000 --- a/src/main/webapp/app/exercises/programming/hestia/generation-overview/steps/code-hint-generation-step/code-hint-generation-step.component.html +++ /dev/null @@ -1,74 +0,0 @@ -@if (isLoading) { -
      - -
      -} @else { -
      - @if (codeHints?.length) { - - - } @else { - - } -
      - @if (codeHints?.length) { - - - - {{ 'artemisApp.codeHint.management.step4.title' | artemisTranslate }} - {{ 'artemisApp.codeHint.management.step4.task' | artemisTranslate }} - - - @for (codeHint of codeHints; track codeHint) { - - - - {{ codeHint.title }} - - - {{ codeHint?.programmingExerciseTask?.taskName }} - - -
      - @if (codeHint?.solutionEntries) { -
      - -
      - } - -
      -
      - } -
      - } @else { -
      - -
      - } -} diff --git a/src/main/webapp/app/exercises/programming/hestia/generation-overview/steps/code-hint-generation-step/code-hint-generation-step.component.ts b/src/main/webapp/app/exercises/programming/hestia/generation-overview/steps/code-hint-generation-step/code-hint-generation-step.component.ts deleted file mode 100644 index 59e8150e2fe8..000000000000 --- a/src/main/webapp/app/exercises/programming/hestia/generation-overview/steps/code-hint-generation-step/code-hint-generation-step.component.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; -import { ProgrammingExerciseService } from 'app/exercises/programming/manage/services/programming-exercise.service'; -import { CodeHint } from 'app/entities/hestia/code-hint-model'; -import { faWrench } from '@fortawesome/free-solid-svg-icons'; -import { ProgrammingExercise } from 'app/entities/programming/programming-exercise.model'; -import { CodeHintService } from 'app/exercises/shared/exercise-hint/services/code-hint.service'; -import { AlertService } from 'app/core/util/alert.service'; - -@Component({ - selector: 'jhi-code-hint-generation-step', - templateUrl: './code-hint-generation-step.component.html', - styleUrls: ['../../code-hint-generation-overview/code-hint-generation-overview.component.scss'], -}) -export class CodeHintGenerationStepComponent implements OnInit { - @Input() - exercise: ProgrammingExercise; - - @Output() - onCodeHintsLoaded = new EventEmitter(); - - isLoading = false; - codeHints?: CodeHint[]; - - faWrench = faWrench; - - constructor( - private exerciseService: ProgrammingExerciseService, - private codeHintService: CodeHintService, - private alertService: AlertService, - ) {} - - ngOnInit() { - this.isLoading = true; - this.exerciseService.getCodeHintsForExercise(this.exercise.id!).subscribe({ - next: (codeHints) => { - this.codeHints = codeHints; - this.onCodeHintsLoaded.emit(this.codeHints); - this.isLoading = false; - }, - error: (error) => { - this.isLoading = false; - this.alertService.error(error.message); - }, - }); - } - - generateCodeHints(deleteOldHints: boolean, buttonTranslationKey: string) { - this.isLoading = true; - // eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain - this.codeHintService.generateCodeHintsForExercise(this.exercise?.id!, deleteOldHints).subscribe({ - next: (generatedHints) => { - if (deleteOldHints) { - this.codeHints = generatedHints; - } else { - generatedHints.forEach((generatedHint) => this.codeHints?.push(generatedHint)); - } - this.onCodeHintsLoaded.emit(this.codeHints); - this.isLoading = false; - this.alertService.success('artemisApp.codeHint.management.step4.' + buttonTranslationKey + '.success'); - }, - error: (error) => { - this.isLoading = false; - this.alertService.error(error.message); - }, - }); - } -} diff --git a/src/main/webapp/app/exercises/programming/hestia/generation-overview/steps/coverage-generation-step/coverage-generation-step.component.html b/src/main/webapp/app/exercises/programming/hestia/generation-overview/steps/coverage-generation-step/coverage-generation-step.component.html deleted file mode 100644 index 421eb8140c86..000000000000 --- a/src/main/webapp/app/exercises/programming/hestia/generation-overview/steps/coverage-generation-step/coverage-generation-step.component.html +++ /dev/null @@ -1,13 +0,0 @@ -@if (isLoading) { -
      - -
      -} -@if (coverageReport && fileContentByPath) { - -} -@if (!(coverageReport || fileContentByPath)) { -
      - -
      -} diff --git a/src/main/webapp/app/exercises/programming/hestia/generation-overview/steps/coverage-generation-step/coverage-generation-step.component.ts b/src/main/webapp/app/exercises/programming/hestia/generation-overview/steps/coverage-generation-step/coverage-generation-step.component.ts deleted file mode 100644 index 0fdc47306615..000000000000 --- a/src/main/webapp/app/exercises/programming/hestia/generation-overview/steps/coverage-generation-step/coverage-generation-step.component.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; -import { CoverageReport } from 'app/entities/hestia/coverage-report.model'; -import { ProgrammingExerciseService } from 'app/exercises/programming/manage/services/programming-exercise.service'; -import { ProgrammingExercise } from 'app/entities/programming/programming-exercise.model'; -import { AlertService } from 'app/core/util/alert.service'; - -@Component({ - selector: 'jhi-coverage-generation-step', - templateUrl: './coverage-generation-step.component.html', - styleUrls: ['../../code-hint-generation-overview/code-hint-generation-overview.component.scss'], -}) -export class CoverageGenerationStepComponent implements OnInit { - @Input() - exercise: ProgrammingExercise; - - @Output() - onCoverageLoaded = new EventEmitter(); - - isLoading = false; - coverageReport?: CoverageReport; - fileContentByPath = new Map(); - - constructor( - private exerciseService: ProgrammingExerciseService, - private alertService: AlertService, - ) {} - - ngOnInit(): void { - this.isLoading = true; - this.exerciseService.getSolutionRepositoryTestFilesWithContent(this.exercise.id!).subscribe({ - next: (filesWithContent: Map) => { - this.exerciseService.getLatestFullTestwiseCoverageReport(this.exercise.id!).subscribe({ - next: (coverageReport) => { - this.isLoading = false; - this.onCoverageLoaded.emit(coverageReport); - this.coverageReport = coverageReport; - this.fileContentByPath = filesWithContent; - }, - error: (error) => { - this.isLoading = false; - this.alertService.error(error.message); - }, - }); - }, - }); - } -} diff --git a/src/main/webapp/app/exercises/programming/hestia/generation-overview/steps/diff-generation-step/diff-generation-step.component.html b/src/main/webapp/app/exercises/programming/hestia/generation-overview/steps/diff-generation-step/diff-generation-step.component.html deleted file mode 100644 index af31d173e0da..000000000000 --- a/src/main/webapp/app/exercises/programming/hestia/generation-overview/steps/diff-generation-step/diff-generation-step.component.html +++ /dev/null @@ -1,15 +0,0 @@ -@if (isLoading) { -
      - -
      -} -@if (!isLoading && gitDiffReport) { -
      - -
      -} -@if (!isLoading && !gitDiffReport) { -
      - -
      -} diff --git a/src/main/webapp/app/exercises/programming/hestia/generation-overview/steps/diff-generation-step/diff-generation-step.component.ts b/src/main/webapp/app/exercises/programming/hestia/generation-overview/steps/diff-generation-step/diff-generation-step.component.ts deleted file mode 100644 index 3903491a1477..000000000000 --- a/src/main/webapp/app/exercises/programming/hestia/generation-overview/steps/diff-generation-step/diff-generation-step.component.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; -import { ProgrammingExerciseService } from 'app/exercises/programming/manage/services/programming-exercise.service'; -import { ProgrammingExercise } from 'app/entities/programming/programming-exercise.model'; -import { AlertService } from 'app/core/util/alert.service'; -import { ProgrammingExerciseGitDiffReport } from 'app/entities/hestia/programming-exercise-git-diff-report.model'; - -@Component({ - selector: 'jhi-diff-generation-step', - templateUrl: './diff-generation-step.component.html', - styleUrls: ['../../code-hint-generation-overview/code-hint-generation-overview.component.scss'], -}) -export class DiffGenerationStepComponent implements OnInit { - @Input() - exercise: ProgrammingExercise; - - @Output() - onGitDiffLoaded = new EventEmitter(); - - isLoading = false; - gitDiffReport?: ProgrammingExerciseGitDiffReport; - templateFileContentByPath: Map; - solutionFileContentByPath: Map; - - constructor( - private exerciseService: ProgrammingExerciseService, - private alertService: AlertService, - ) {} - - ngOnInit() { - this.isLoading = true; - this.exerciseService.getDiffReport(this.exercise.id!).subscribe({ - next: (report) => { - this.gitDiffReport = report; - this.onGitDiffLoaded.emit(report); - this.exerciseService.getTemplateRepositoryTestFilesWithContent(this.exercise.id!).subscribe({ - next: (response: Map) => { - this.templateFileContentByPath = response; - this.isLoading = this.solutionFileContentByPath === undefined; - }, - }); - this.exerciseService.getSolutionRepositoryTestFilesWithContent(this.exercise.id!).subscribe({ - next: (response: Map) => { - this.solutionFileContentByPath = response; - this.isLoading = this.templateFileContentByPath === undefined; - }, - }); - }, - error: (error) => { - this.isLoading = false; - this.alertService.error(error.message); - }, - }); - } -} diff --git a/src/main/webapp/app/exercises/programming/hestia/generation-overview/steps/solution-entry-generation-step/solution-entry-generation-step.component.html b/src/main/webapp/app/exercises/programming/hestia/generation-overview/steps/solution-entry-generation-step/solution-entry-generation-step.component.html deleted file mode 100644 index 3e3b36f53456..000000000000 --- a/src/main/webapp/app/exercises/programming/hestia/generation-overview/steps/solution-entry-generation-step/solution-entry-generation-step.component.html +++ /dev/null @@ -1,97 +0,0 @@ -@if (isLoading) { -
      - -
      -} -
      - - - @if (!!exercise?.buildConfig?.testwiseCoverageEnabled) { - - } - -
      -
      - - - - - - - - - - - - @for (entry of solutionEntries; track entry) { - - - - - - - - - } - -
      - {{ 'artemisApp.programmingExerciseTestCase.testName' | artemisTranslate }} - - File Path - - -
      {{ entry?.id }}{{ entry?.testCase?.testName }}{{ entry?.filePath }} - {{ - (entry.code?.split('\n')?.length! === 1 ? 'artemisApp.codeHint.lineOfCode' : 'artemisApp.codeHint.linesOfCode') - | artemisTranslate: { lines: entry.code?.split('\n')?.length! } - }} - {{ 'artemisApp.codeHint.entryAbbreviation.' + entry.testCase?.type?.toString()?.toLocaleLowerCase() | artemisTranslate }} -
      - - - -
      -
      - @if (!solutionEntries?.length) { -
      - -
      - } -
      diff --git a/src/main/webapp/app/exercises/programming/hestia/generation-overview/steps/solution-entry-generation-step/solution-entry-generation-step.component.ts b/src/main/webapp/app/exercises/programming/hestia/generation-overview/steps/solution-entry-generation-step/solution-entry-generation-step.component.ts deleted file mode 100644 index 5d4b3c68f675..000000000000 --- a/src/main/webapp/app/exercises/programming/hestia/generation-overview/steps/solution-entry-generation-step/solution-entry-generation-step.component.ts +++ /dev/null @@ -1,178 +0,0 @@ -import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'; -import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; -import { ProgrammingExerciseSolutionEntry } from 'app/entities/hestia/programming-exercise-solution-entry.model'; -import { SolutionEntryDetailsModalComponent } from 'app/exercises/programming/hestia/generation-overview/solution-entry-details-modal/solution-entry-details-modal.component'; -import { ProgrammingExerciseService } from 'app/exercises/programming/manage/services/programming-exercise.service'; -import { ProgrammingExerciseTestCaseType } from 'app/entities/programming/programming-exercise-test-case.model'; -import { ProgrammingExercise } from 'app/entities/programming/programming-exercise.model'; -import { AlertService } from 'app/core/util/alert.service'; -import { Subject } from 'rxjs'; -import { faSort, faSortDown, faSortUp, faTimes } from '@fortawesome/free-solid-svg-icons'; -import { CodeHintService } from 'app/exercises/shared/exercise-hint/services/code-hint.service'; -import { ManualSolutionEntryCreationModalComponent } from 'app/exercises/programming/hestia/generation-overview/manual-solution-entry-creation-modal/manual-solution-entry-creation-modal.component'; -import { SortingOrder } from 'app/shared/table/pageable-table'; -import { ProgrammingExerciseSolutionEntryService } from 'app/exercises/shared/exercise-hint/services/programming-exercise-solution-entry.service'; -import { ConfirmAutofocusModalComponent } from 'app/shared/components/confirm-autofocus-modal.component'; -import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; - -@Component({ - selector: 'jhi-solution-entry-generation-step', - templateUrl: './solution-entry-generation-step.component.html', - styleUrls: ['../../code-hint-generation-overview/code-hint-generation-overview.component.scss'], -}) -export class SolutionEntryGenerationStepComponent implements OnInit, OnDestroy { - @Input() - exercise: ProgrammingExercise; - - @Output() - onEntryUpdate = new EventEmitter(); - - isLoading: boolean; - solutionEntries: ProgrammingExerciseSolutionEntry[]; - faTimes = faTimes; - - testCaseSortOrder?: SortingOrder; - faSort = faSort; - faSortUp = faSortUp; - faSortDown = faSortDown; - - private dialogErrorSource = new Subject(); - dialogError$ = this.dialogErrorSource.asObservable(); - - readonly SortingOrder = SortingOrder; - - constructor( - private modalService: NgbModal, - private exerciseService: ProgrammingExerciseService, - private alertService: AlertService, - private artemisTranslatePipe: ArtemisTranslatePipe, - private codeHintService: CodeHintService, - private solutionEntryService: ProgrammingExerciseSolutionEntryService, - ) {} - - ngOnInit() { - this.isLoading = true; - this.solutionEntryService.getSolutionEntriesForExercise(this.exercise.id!).subscribe({ - next: (solutionEntries: ProgrammingExerciseSolutionEntry[]) => { - this.solutionEntries = solutionEntries; - this.isLoading = false; - this.onEntryUpdate.emit(this.solutionEntries); - }, - error: (error) => { - this.isLoading = false; - this.alertService.error(error.message); - }, - }); - } - - ngOnDestroy(): void { - this.dialogErrorSource.unsubscribe(); - } - - openSolutionEntryModal(solutionEntry: ProgrammingExerciseSolutionEntry, isEditable: boolean) { - const modalRef: NgbModalRef = this.modalService.open(SolutionEntryDetailsModalComponent as Component, { - size: 'lg', - backdrop: 'static', - }); - modalRef.componentInstance.exerciseId = this.exercise.id; - modalRef.componentInstance.solutionEntry = solutionEntry; - modalRef.componentInstance.isEditable = isEditable; - } - - openManualEntryCreationModal() { - const modalRef: NgbModalRef = this.modalService.open(ManualSolutionEntryCreationModalComponent as Component, { - size: 'lg', - backdrop: 'static', - }); - modalRef.componentInstance.exerciseId = this.exercise.id; - modalRef.componentInstance.onEntryCreated.subscribe((createdEntry: ProgrammingExerciseSolutionEntry) => { - this.solutionEntries.push(createdEntry); - this.onEntryUpdate.emit(this.solutionEntries); - }); - } - - onGenerateStructuralSolutionEntries() { - this.exerciseService.createStructuralSolutionEntries(this.exercise.id!).subscribe({ - next: (updatedStructuralEntries) => { - this.alertService.success('artemisApp.programmingExercise.createStructuralSolutionEntriesSuccess'); - // replace all structural entries - const result = this.removeSolutionEntriesOfTypeFromArray(this.solutionEntries, ProgrammingExerciseTestCaseType.STRUCTURAL); - Array.prototype.push.apply(result, updatedStructuralEntries); - this.solutionEntries = result; - this.testCaseSortOrder = undefined; - this.onEntryUpdate.emit(this.solutionEntries); - }, - error: (error) => this.alertService.error(error.message), - }); - } - - onGenerateBehavioralSolutionEntries() { - this.exerciseService.createBehavioralSolutionEntries(this.exercise.id!).subscribe({ - next: (updatedBehavioralEntries) => { - this.alertService.success('artemisApp.programmingExercise.createBehavioralSolutionEntriesSuccess'); - // replace all behavioral entries - const result = this.removeSolutionEntriesOfTypeFromArray(this.solutionEntries, ProgrammingExerciseTestCaseType.BEHAVIORAL); - Array.prototype.push.apply(result, updatedBehavioralEntries); - this.solutionEntries = result; - this.testCaseSortOrder = undefined; - this.onEntryUpdate.emit(this.solutionEntries); - }, - error: (error) => this.alertService.error(error.message), - }); - } - - openBulkDeletionModal() { - const modalRef = this.modalService.open(ConfirmAutofocusModalComponent, { keyboard: true, size: 'lg' }); - modalRef.componentInstance.title = 'artemisApp.codeHint.management.step3.deleteAllEntriesButton.title'; - modalRef.componentInstance.text = this.artemisTranslatePipe.transform('artemisApp.codeHint.management.step3.deleteAllEntriesButton.question'); - modalRef.result.then(() => { - this.deleteAllSolutionEntries(); - }); - } - - deleteAllSolutionEntries() { - // eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain - this.solutionEntryService.deleteAllSolutionEntriesForExercise(this.exercise?.id!).subscribe({ - next: () => { - this.solutionEntries = []; - this.onEntryUpdate.emit([]); - this.alertService.success('artemisApp.codeHint.management.step3.deleteAllEntriesButton.success'); - this.dialogErrorSource.next(''); - }, - error: (error) => this.dialogErrorSource.error(error.message), - }); - } - - deleteSolutionEntry(entry: ProgrammingExerciseSolutionEntry) { - // eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain - this.solutionEntryService.deleteSolutionEntry(this.exercise?.id!, entry.testCase?.id!, entry.id!).subscribe({ - next: () => { - this.solutionEntries = this.solutionEntries.filter((existingEntry) => entry !== existingEntry); - this.dialogErrorSource.next(''); - this.onEntryUpdate.emit(this.solutionEntries); - }, - error: (error) => this.dialogErrorSource.error(error.message), - }); - } - - changeTestCaseSortOrder() { - switch (this.testCaseSortOrder) { - case SortingOrder.ASCENDING: - this.solutionEntries = this.solutionEntries.reverse(); - this.testCaseSortOrder = SortingOrder.DESCENDING; - break; - case SortingOrder.DESCENDING: - this.solutionEntries = this.solutionEntries.reverse(); - this.testCaseSortOrder = SortingOrder.ASCENDING; - break; - case undefined: - // eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain - this.solutionEntries = this.solutionEntries.sort((a, b) => a.testCase?.testName!.localeCompare(b.testCase?.testName!)!); - this.testCaseSortOrder = SortingOrder.ASCENDING; - } - } - - private removeSolutionEntriesOfTypeFromArray(entries: ProgrammingExerciseSolutionEntry[], typeToRemove: ProgrammingExerciseTestCaseType) { - return entries.filter((entry) => entry.testCase?.type !== typeToRemove); - } -} diff --git a/src/main/webapp/app/exercises/programming/hestia/testwise-coverage-report/testwise-coverage-file.component.html b/src/main/webapp/app/exercises/programming/hestia/testwise-coverage-report/testwise-coverage-file.component.html deleted file mode 100644 index 3690bfdf0e3b..000000000000 --- a/src/main/webapp/app/exercises/programming/hestia/testwise-coverage-report/testwise-coverage-file.component.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - {{ fileName }} - - {{ proportionString }} - -
      - -
      -
      diff --git a/src/main/webapp/app/exercises/programming/hestia/testwise-coverage-report/testwise-coverage-file.component.scss b/src/main/webapp/app/exercises/programming/hestia/testwise-coverage-report/testwise-coverage-file.component.scss deleted file mode 100644 index 915e18b1199e..000000000000 --- a/src/main/webapp/app/exercises/programming/hestia/testwise-coverage-report/testwise-coverage-file.component.scss +++ /dev/null @@ -1,20 +0,0 @@ -.mat-expansion-panel-header { - padding: 0; -} - -.mat-expansion-panel .mat-expansion-panel-body { - padding: 0 !important; -} - -.mat-expansion-panel-header-description { - flex-grow: 0; -} - -.mat-expansion-panel .mat-expansion-indicator::after { - margin-right: 2px !important; - margin-left: 2px !important; -} - -.covered-line-highlight { - background-color: var(--monaco-editor-test-coverage-highlight); -} diff --git a/src/main/webapp/app/exercises/programming/hestia/testwise-coverage-report/testwise-coverage-file.component.ts b/src/main/webapp/app/exercises/programming/hestia/testwise-coverage-report/testwise-coverage-file.component.ts deleted file mode 100644 index b9fc28de4fbf..000000000000 --- a/src/main/webapp/app/exercises/programming/hestia/testwise-coverage-report/testwise-coverage-file.component.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { Component, Input, OnChanges, OnInit, SimpleChanges, ViewChild, ViewEncapsulation } from '@angular/core'; -import { CoverageFileReport } from 'app/entities/hestia/coverage-file-report.model'; -import { MonacoEditorComponent } from 'app/shared/monaco-editor/monaco-editor.component'; - -@Component({ - selector: 'jhi-testwise-coverage-file', - templateUrl: './testwise-coverage-file.component.html', - styleUrls: ['./testwise-coverage-file.component.scss'], - encapsulation: ViewEncapsulation.None, -}) -export class TestwiseCoverageFileComponent implements OnInit, OnChanges { - @Input() - fileContent: string; - - @Input() - fileName: string; - - @Input() - fileReport: CoverageFileReport; - - @ViewChild('editor', { static: true }) - editor: MonacoEditorComponent; - - proportionCoveredLines: number; - proportionString: string; - editorHeight: number = 20; - - static readonly COVERED_LINE_HIGHLIGHT_CLASS = 'covered-line-highlight'; - - ngOnInit(): void { - this.renderFile(); - this.editorHeight = this.editor.getContentHeight(); - } - - ngOnChanges(changes: SimpleChanges): void { - if (changes.fileReport || changes.fileContent) { - this.renderFile(); - } - } - - private aggregateCoveredLinesBlocks(fileReport: CoverageFileReport): Map { - const coveredLines = new Set(); - const entries = fileReport.testwiseCoverageEntries!; - // retrieve all covered line numbers - entries.forEach((entry) => { - this.getRangeArray(entry.startLine!, entry.lineCount!).forEach((line) => coveredLines.add(line)); - }); - - // build the blocks - const orderedLines = Array.from(coveredLines).sort(); - const startLineByLength = new Map(); - - // set the covered line ratio accordingly - this.proportionCoveredLines = orderedLines.length / this.fileReport!.lineCount!; - this.proportionString = `${(this.proportionCoveredLines * 100).toFixed(1)} %`; - - let index = 0; - while (index < orderedLines.length) { - const currentBlockStartLine = orderedLines[index]; - let currentBlockLength = 1; - let continueBlock = true; - - // count the length of the consecutive blocks - while (continueBlock && index < orderedLines.length) { - if (index + 1 === orderedLines.length) { - continueBlock = false; - } else if (orderedLines[index + 1] === orderedLines[index] + 1) { - currentBlockLength++; - } else { - continueBlock = false; - } - index++; - } - startLineByLength.set(currentBlockStartLine, currentBlockLength); - } - - return startLineByLength; - } - - private getRangeArray(startLine: number, lineCount: number): number[] { - return [...Array(lineCount).keys()].map((i) => i + startLine - 1); - } - - private renderFile() { - this.editor.changeModel(this.fileName, this.fileContent ?? ''); - this.editor.disposeLineHighlights(); - this.aggregateCoveredLinesBlocks(this.fileReport).forEach((blockLength, lineNumber) => { - this.editor.highlightLines(lineNumber + 1, lineNumber + blockLength, 'covered-line-highlight', 'covered-line-highlight'); - }); - } -} diff --git a/src/main/webapp/app/exercises/programming/hestia/testwise-coverage-report/testwise-coverage-report-modal.component.html b/src/main/webapp/app/exercises/programming/hestia/testwise-coverage-report/testwise-coverage-report-modal.component.html deleted file mode 100644 index 01e96e404923..000000000000 --- a/src/main/webapp/app/exercises/programming/hestia/testwise-coverage-report/testwise-coverage-report-modal.component.html +++ /dev/null @@ -1,11 +0,0 @@ -
      - - -
      diff --git a/src/main/webapp/app/exercises/programming/hestia/testwise-coverage-report/testwise-coverage-report-modal.component.ts b/src/main/webapp/app/exercises/programming/hestia/testwise-coverage-report/testwise-coverage-report-modal.component.ts deleted file mode 100644 index 2183198c8772..000000000000 --- a/src/main/webapp/app/exercises/programming/hestia/testwise-coverage-report/testwise-coverage-report-modal.component.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Component, Input } from '@angular/core'; -import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; -import { CoverageReport } from 'app/entities/hestia/coverage-report.model'; - -@Component({ - selector: 'jhi-testwise-coverage-report-modal', - templateUrl: './testwise-coverage-report-modal.component.html', -}) -export class TestwiseCoverageReportModalComponent { - @Input() - report: CoverageReport; - - @Input() - fileContentByPath: Map; - - constructor(protected activeModal: NgbActiveModal) {} - - close(): void { - this.activeModal.dismiss(); - } -} diff --git a/src/main/webapp/app/exercises/programming/hestia/testwise-coverage-report/testwise-coverage-report.component.html b/src/main/webapp/app/exercises/programming/hestia/testwise-coverage-report/testwise-coverage-report.component.html deleted file mode 100644 index b16a1eccd579..000000000000 --- a/src/main/webapp/app/exercises/programming/hestia/testwise-coverage-report/testwise-coverage-report.component.html +++ /dev/null @@ -1,17 +0,0 @@ - -@for (entry of displayedTestCaseNames | keyvalue; track entry) { -
      - - {{ entry.key }} -
      -} -
      -
      -
      -
      -
      -@for (entry of fileReportByFileName | keyvalue; track identifyCoverageFileComponent($index, entry)) { -
      - -
      -} diff --git a/src/main/webapp/app/exercises/programming/hestia/testwise-coverage-report/testwise-coverage-report.component.ts b/src/main/webapp/app/exercises/programming/hestia/testwise-coverage-report/testwise-coverage-report.component.ts deleted file mode 100644 index a5f6b75f05a7..000000000000 --- a/src/main/webapp/app/exercises/programming/hestia/testwise-coverage-report/testwise-coverage-report.component.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { Component, Input, OnInit } from '@angular/core'; -import { CoverageReport } from 'app/entities/hestia/coverage-report.model'; -import { CoverageFileReport } from 'app/entities/hestia/coverage-file-report.model'; - -@Component({ - selector: 'jhi-testwise-coverage-report', - templateUrl: './testwise-coverage-report.component.html', -}) -export class TestwiseCoverageReportComponent implements OnInit { - @Input() - report: CoverageReport; - - @Input() - fileContentByPath: Map; - - displayedTestCaseNames: Map; - - fileReportByFileName: Map; - - constructor() {} - - ngOnInit(): void { - // initially display covered lines for all test cases - this.displayedTestCaseNames = new Map(); - // retrieve all test cases - const testCases = new Set( - this.report?.fileReports - ?.flatMap((report) => report) - .flatMap((fileReport) => fileReport.testwiseCoverageEntries) - .map((entry) => entry!.testCase), - ); - testCases.forEach((testCase) => this.displayedTestCaseNames.set(testCase!.testName!, true)); - - this.setReportsByFileName(); - } - - changeReportsBySelectedTestCases(testCaseName: string): void { - const selected = this.displayedTestCaseNames.get(testCaseName); - this.displayedTestCaseNames.set(testCaseName, !selected); - this.setReportsByFileName(); - } - - private setReportsByFileName(): void { - const result = new Map(); - // create the reports for all files, not only for files that have existing coverage data - this.fileContentByPath?.forEach((content, filePath) => { - // do not include non-java/kotlin files - if (!(filePath.endsWith('.java') || filePath.endsWith('.kt'))) { - return; - } - const matchingFileReport = this.report.fileReports?.filter((fileReport) => fileReport?.filePath === filePath)?.first(); - const copiedFileReport = new CoverageFileReport(); - copiedFileReport.filePath = filePath; - if (matchingFileReport) { - copiedFileReport.lineCount = matchingFileReport.lineCount; - copiedFileReport.coveredLineCount = matchingFileReport.coveredLineCount; - - // filter out entries for the current file report - copiedFileReport.testwiseCoverageEntries = matchingFileReport.testwiseCoverageEntries?.filter((entry) => - this.displayedTestCaseNames.get(entry.testCase!.testName!), - ); - } else { - copiedFileReport.lineCount = content.split('\n').length; - copiedFileReport.coveredLineCount = 0; - copiedFileReport.testwiseCoverageEntries = []; - } - result.set(filePath, copiedFileReport); - }); - this.fileReportByFileName = result; - } - - identifyCoverageFileComponent(index: number, item: any) { - return item.key; - } -} diff --git a/src/main/webapp/app/exercises/programming/hestia/testwise-coverage-report/testwise-coverage-report.module.ts b/src/main/webapp/app/exercises/programming/hestia/testwise-coverage-report/testwise-coverage-report.module.ts deleted file mode 100644 index c37bc7a255da..000000000000 --- a/src/main/webapp/app/exercises/programming/hestia/testwise-coverage-report/testwise-coverage-report.module.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { NgModule } from '@angular/core'; -import { ArtemisSharedModule } from 'app/shared/shared.module'; -import { TestwiseCoverageReportModalComponent } from 'app/exercises/programming/hestia/testwise-coverage-report/testwise-coverage-report-modal.component'; -import { TestwiseCoverageReportComponent } from 'app/exercises/programming/hestia/testwise-coverage-report/testwise-coverage-report.component'; -import { TestwiseCoverageFileComponent } from 'app/exercises/programming/hestia/testwise-coverage-report/testwise-coverage-file.component'; -import { MatExpansionModule } from '@angular/material/expansion'; -import { MonacoEditorComponent } from 'app/shared/monaco-editor/monaco-editor.component'; - -@NgModule({ - imports: [ArtemisSharedModule, MatExpansionModule, MonacoEditorComponent], - declarations: [TestwiseCoverageFileComponent, TestwiseCoverageReportComponent, TestwiseCoverageReportModalComponent], - exports: [TestwiseCoverageFileComponent, TestwiseCoverageReportModalComponent, TestwiseCoverageReportComponent], -}) -export class TestwiseCoverageReportModule {} diff --git a/src/main/webapp/app/exercises/programming/manage/grading/tasks/programming-exercise-grading-tasks-table.component.ts b/src/main/webapp/app/exercises/programming/manage/grading/tasks/programming-exercise-grading-tasks-table.component.ts index bd491c999df9..ec88a4dc9ae1 100644 --- a/src/main/webapp/app/exercises/programming/manage/grading/tasks/programming-exercise-grading-tasks-table.component.ts +++ b/src/main/webapp/app/exercises/programming/manage/grading/tasks/programming-exercise-grading-tasks-table.component.ts @@ -8,7 +8,7 @@ import { ProgrammingExerciseTask } from './programming-exercise-task'; import { Observable, Subject } from 'rxjs'; import { ProgrammingExerciseTestCase } from 'app/entities/programming/programming-exercise-test-case.model'; import { isExamExercise } from 'app/shared/util/utils'; -import { ProgrammingExerciseServerSideTask } from 'app/entities/hestia/programming-exercise-task.model'; +import { ProgrammingExerciseServerSideTask } from 'app/entities/programming-exercise-task.model'; type Sort = { by: 'name' | 'weight' | 'multiplier' | 'bonusPoints' | 'visibility' | 'resulting' | 'type'; diff --git a/src/main/webapp/app/exercises/programming/manage/grading/tasks/programming-exercise-task.service.ts b/src/main/webapp/app/exercises/programming/manage/grading/tasks/programming-exercise-task.service.ts index 5f45d551b476..3021e5e548f2 100644 --- a/src/main/webapp/app/exercises/programming/manage/grading/tasks/programming-exercise-task.service.ts +++ b/src/main/webapp/app/exercises/programming/manage/grading/tasks/programming-exercise-task.service.ts @@ -1,6 +1,6 @@ import { HttpClient, HttpErrorResponse } from '@angular/common/http'; import { Injectable } from '@angular/core'; -import { ProgrammingExerciseServerSideTask } from 'app/entities/hestia/programming-exercise-task.model'; +import { ProgrammingExerciseServerSideTask } from 'app/entities/programming-exercise-task.model'; import { Observable, catchError, of, tap } from 'rxjs'; import { Exercise } from 'app/entities/exercise.model'; import { ProgrammingExerciseTask } from 'app/exercises/programming/manage/grading/tasks/programming-exercise-task'; diff --git a/src/main/webapp/app/exercises/programming/manage/grading/tasks/programming-exercise-task.ts b/src/main/webapp/app/exercises/programming/manage/grading/tasks/programming-exercise-task.ts index 3a5844ddbb9e..101498cc8cf4 100644 --- a/src/main/webapp/app/exercises/programming/manage/grading/tasks/programming-exercise-task.ts +++ b/src/main/webapp/app/exercises/programming/manage/grading/tasks/programming-exercise-task.ts @@ -1,4 +1,4 @@ -import { ProgrammingExerciseServerSideTask } from 'app/entities/hestia/programming-exercise-task.model'; +import { ProgrammingExerciseServerSideTask } from 'app/entities/programming-exercise-task.model'; import { TestCaseStats } from 'app/entities/programming/programming-exercise-test-case-statistics.model'; import { ProgrammingExerciseTestCase, ProgrammingExerciseTestCaseType, Visibility } from 'app/entities/programming/programming-exercise-test-case.model'; diff --git a/src/main/webapp/app/exercises/programming/manage/programming-exercise-detail.component.html b/src/main/webapp/app/exercises/programming/manage/programming-exercise-detail.component.html index 95da3dea7a71..d3809ff3fdc8 100644 --- a/src/main/webapp/app/exercises/programming/manage/programming-exercise-detail.component.html +++ b/src/main/webapp/app/exercises/programming/manage/programming-exercise-detail.component.html @@ -40,15 +40,6 @@

      {{ } - @if (programmingExercise.course && !isExamExercise) { - - - - - } @@ -134,27 +125,6 @@

      {{ } - @if (programmingExercise.isAtLeastEditor) { - - @if ( - (programmingExercise.programmingLanguage === ProgrammingLanguage.JAVA || - programmingExercise.programmingLanguage === ProgrammingLanguage.KOTLIN) && - programmingExercise.buildConfig?.testwiseCoverageEnabled - ) { - - - - } - - } @if (programmingExercise.isAtLeastInstructor && !localVCEnabled) {

    } - @if (programmingExercise.isAtLeastEditor) { - - - - - }
    @if (programmingExercise.isAtLeastEditor && programmingExercise.templateParticipation) { diff --git a/src/main/webapp/app/exercises/programming/manage/services/programming-exercise.service.ts b/src/main/webapp/app/exercises/programming/manage/services/programming-exercise.service.ts index 5b1a793288ff..0ce5e833273f 100644 --- a/src/main/webapp/app/exercises/programming/manage/services/programming-exercise.service.ts +++ b/src/main/webapp/app/exercises/programming/manage/services/programming-exercise.service.ts @@ -13,13 +13,8 @@ import { SolutionProgrammingExerciseParticipation } from 'app/entities/participa import { TextPlagiarismResult } from 'app/exercises/shared/plagiarism/types/text/TextPlagiarismResult'; import { PlagiarismOptions } from 'app/exercises/shared/plagiarism/types/PlagiarismOptions'; import { Submission } from 'app/entities/submission.model'; -import { ProgrammingExerciseGitDiffReport } from 'app/entities/hestia/programming-exercise-git-diff-report.model'; -import { CoverageReport } from 'app/entities/hestia/coverage-report.model'; -import { ProgrammingExerciseSolutionEntry } from 'app/entities/hestia/programming-exercise-solution-entry.model'; -import { ProgrammingExerciseServerSideTask } from 'app/entities/hestia/programming-exercise-task.model'; +import { ProgrammingExerciseGitDiffReport } from 'app/entities/programming-exercise-git-diff-report.model'; import { convertDateFromClient, convertDateFromServer } from 'app/utils/date.utils'; -import { ExerciseHint } from 'app/entities/hestia/exercise-hint.model'; -import { ProgrammingExerciseTestCase } from 'app/entities/programming/programming-exercise-test-case.model'; import { BuildLogStatisticsDTO } from 'app/entities/programming/build-log-statistics-dto'; import { SortService } from 'app/shared/service/sort.service'; import { Result } from 'app/entities/result.model'; @@ -533,17 +528,6 @@ export class ProgrammingExerciseService { return exerciseRes; } - /** - * Get tasks and tests cases extracted from the problem statement for a programming exercise. - * This method and all helper methods are only for testing reason and will be removed later on. - * @param exerciseId the exercise id - */ - getTasksAndTestsExtractedFromProblemStatement(exerciseId: number): Observable { - return this.http - .get(`${this.resourceUrl}/${exerciseId}/tasks`, { observe: 'response' }) - .pipe(map((res: HttpResponse) => res.body ?? [])); - } - /** * Gets the git-diff report of a programming exercise * @@ -612,14 +596,6 @@ export class ProgrammingExerciseService { .pipe(map((res: HttpResponse) => res.body ?? undefined)); } - /** - * Gets the testwise coverage report of a programming exercise for the latest solution submission with all descending reports - * @param exerciseId The id of a programming exercise - */ - getLatestFullTestwiseCoverageReport(exerciseId: number): Observable { - return this.http.get(`${this.resourceUrl}/${exerciseId}/full-testwise-coverage-report`); - } - /** * Gets all files from the last solution participation repository */ @@ -646,26 +622,6 @@ export class ProgrammingExerciseService { ); } - getSolutionFileNames(exerciseId: number): Observable { - return this.http.get(`${this.resourceUrl}/${exerciseId}/file-names`); - } - - getCodeHintsForExercise(exerciseId: number): Observable { - return this.http.get(`${this.resourceUrl}/${exerciseId}/exercise-hints`); - } - - createStructuralSolutionEntries(exerciseId: number): Observable { - return this.http.post(`${this.resourceUrl}/${exerciseId}/structural-solution-entries`, null); - } - - createBehavioralSolutionEntries(exerciseId: number): Observable { - return this.http.post(`${this.resourceUrl}/${exerciseId}/behavioral-solution-entries`, null); - } - - getAllTestCases(exerciseId: number): Observable { - return this.http.get(`api/programming-exercises/${exerciseId}/test-cases`); - } - getBuildLogStatistics(exerciseId: number): Observable { return this.http.get(`${this.resourceUrl}/${exerciseId}/build-log-statistics`); } diff --git a/src/main/webapp/app/exercises/programming/manage/update/programming-exercise-creation-config.ts b/src/main/webapp/app/exercises/programming/manage/update/programming-exercise-creation-config.ts index 51b9056d0b15..79111d8a92c2 100644 --- a/src/main/webapp/app/exercises/programming/manage/update/programming-exercise-creation-config.ts +++ b/src/main/webapp/app/exercises/programming/manage/update/programming-exercise-creation-config.ts @@ -37,7 +37,6 @@ export type ProgrammingExerciseCreationConfig = { onStaticCodeAnalysisChanged: () => void; maxPenaltyPattern: string; sequentialTestRunsAllowed: boolean; - testwiseCoverageAnalysisSupported: boolean; problemStatementLoaded: boolean; templateParticipationResultLoaded: boolean; hasUnsavedChanges: boolean; diff --git a/src/main/webapp/app/exercises/programming/manage/update/programming-exercise-update.component.ts b/src/main/webapp/app/exercises/programming/manage/update/programming-exercise-update.component.ts index b5aa5d3bb090..02d2035b582c 100644 --- a/src/main/webapp/app/exercises/programming/manage/update/programming-exercise-update.component.ts +++ b/src/main/webapp/app/exercises/programming/manage/update/programming-exercise-update.component.ts @@ -143,7 +143,6 @@ export class ProgrammingExerciseUpdateComponent implements AfterViewInit, OnDest public checkoutSolutionRepositoryAllowed = false; public customizeBuildPlanWithAeolus = false; public sequentialTestRunsAllowed = false; - public testwiseCoverageAnalysisSupported = false; public auxiliaryRepositoriesSupported = false; auxiliaryRepositoriesValid = signal(true); public customBuildPlansSupported: string = ''; @@ -278,7 +277,6 @@ export class ProgrammingExerciseUpdateComponent implements AfterViewInit, OnDest this.staticCodeAnalysisAllowed = programmingLanguageFeature.staticCodeAnalysis; this.checkoutSolutionRepositoryAllowed = programmingLanguageFeature.checkoutSolutionRepositoryAllowed; this.sequentialTestRunsAllowed = programmingLanguageFeature.sequentialTestRuns; - this.testwiseCoverageAnalysisSupported = programmingLanguageFeature.testwiseCoverageAnalysisSupported; this.auxiliaryRepositoriesSupported = programmingLanguageFeature.auxiliaryRepositoriesSupported; // filter out MAVEN_MAVEN and GRADLE_GRADLE because they are not directly selectable but only via a checkbox this.projectTypes = programmingLanguageFeature.projectTypes?.filter((projectType) => projectType !== ProjectType.MAVEN_MAVEN && projectType !== ProjectType.GRADLE_GRADLE); @@ -367,11 +365,9 @@ export class ProgrammingExerciseUpdateComponent implements AfterViewInit, OnDest this.selectedProjectTypeValue = ProjectType.MAVEN_BLACKBOX; this.programmingExercise.projectType = ProjectType.MAVEN_BLACKBOX; this.sequentialTestRunsAllowed = false; - this.testwiseCoverageAnalysisSupported = false; } else if (type === ProjectType.PLAIN_MAVEN || type === ProjectType.MAVEN_MAVEN) { this.selectedProjectTypeValue = ProjectType.PLAIN_MAVEN; this.sequentialTestRunsAllowed = programmingLanguageFeature.sequentialTestRuns; - this.testwiseCoverageAnalysisSupported = programmingLanguageFeature.testwiseCoverageAnalysisSupported; if (this.withDependenciesValue) { this.programmingExercise.projectType = ProjectType.MAVEN_MAVEN; } else { @@ -380,7 +376,6 @@ export class ProgrammingExerciseUpdateComponent implements AfterViewInit, OnDest } else { this.selectedProjectTypeValue = ProjectType.PLAIN_GRADLE; this.sequentialTestRunsAllowed = programmingLanguageFeature.sequentialTestRuns; - this.testwiseCoverageAnalysisSupported = programmingLanguageFeature.testwiseCoverageAnalysisSupported; if (this.withDependenciesValue) { this.programmingExercise.projectType = ProjectType.GRADLE_GRADLE; } else { @@ -1258,7 +1253,6 @@ export class ProgrammingExerciseUpdateComponent implements AfterViewInit, OnDest selectedProjectType: this.selectedProjectType, onProjectTypeChange: this.projectTypeChanged, sequentialTestRunsAllowed: this.sequentialTestRunsAllowed, - testwiseCoverageAnalysisSupported: this.testwiseCoverageAnalysisSupported, staticCodeAnalysisAllowed: this.staticCodeAnalysisAllowed, onStaticCodeAnalysisChanged: this.staticCodeAnalysisChanged, maxPenaltyPattern: this.maxPenaltyPattern, diff --git a/src/main/webapp/app/exercises/programming/manage/update/update-components/custom-build-plans/programming-exercise-custom-aeolus-build-plan.component.ts b/src/main/webapp/app/exercises/programming/manage/update/update-components/custom-build-plans/programming-exercise-custom-aeolus-build-plan.component.ts index ab406d7e0193..0ee09f4dc80b 100644 --- a/src/main/webapp/app/exercises/programming/manage/update/update-components/custom-build-plans/programming-exercise-custom-aeolus-build-plan.component.ts +++ b/src/main/webapp/app/exercises/programming/manage/update/update-components/custom-build-plans/programming-exercise-custom-aeolus-build-plan.component.ts @@ -22,7 +22,6 @@ export class ProgrammingExerciseCustomAeolusBuildPlanComponent implements OnChan projectType?: ProjectType; staticCodeAnalysisEnabled?: boolean; sequentialTestRuns?: boolean; - testwiseCoverageEnabled?: boolean; constructor(private aeolusService: AeolusService) {} @@ -54,8 +53,7 @@ export class ProgrammingExerciseCustomAeolusBuildPlanComponent implements OnChan this.programmingExercise.programmingLanguage !== this.programmingLanguage || this.programmingExercise.projectType !== this.projectType || this.programmingExercise.staticCodeAnalysisEnabled !== this.staticCodeAnalysisEnabled || - this.programmingExercise.buildConfig?.sequentialTestRuns !== this.sequentialTestRuns || - this.programmingExercise.buildConfig?.testwiseCoverageEnabled !== this.testwiseCoverageEnabled + this.programmingExercise.buildConfig?.sequentialTestRuns !== this.sequentialTestRuns ); } @@ -92,18 +90,15 @@ export class ProgrammingExerciseCustomAeolusBuildPlanComponent implements OnChan this.projectType = this.programmingExercise.projectType; this.staticCodeAnalysisEnabled = this.programmingExercise.staticCodeAnalysisEnabled; this.sequentialTestRuns = this.programmingExercise.buildConfig?.sequentialTestRuns; - this.testwiseCoverageEnabled = this.programmingExercise.buildConfig?.testwiseCoverageEnabled; if (!isImportFromFile || !this.programmingExercise.buildConfig?.windfile) { - this.aeolusService - .getAeolusTemplateFile(this.programmingLanguage, this.projectType, this.staticCodeAnalysisEnabled, this.sequentialTestRuns, this.testwiseCoverageEnabled) - .subscribe({ - next: (file) => { - this.programmingExercise.buildConfig!.windfile = this.aeolusService.parseWindFile(file); - }, - error: () => { - this.programmingExercise.buildConfig!.windfile = undefined; - }, - }); + this.aeolusService.getAeolusTemplateFile(this.programmingLanguage, this.projectType, this.staticCodeAnalysisEnabled, this.sequentialTestRuns).subscribe({ + next: (file) => { + this.programmingExercise.buildConfig!.windfile = this.aeolusService.parseWindFile(file); + }, + error: () => { + this.programmingExercise.buildConfig!.windfile = undefined; + }, + }); } this.programmingExerciseCreationConfig.buildPlanLoaded = true; } diff --git a/src/main/webapp/app/exercises/programming/manage/update/update-components/custom-build-plans/programming-exercise-custom-build-plan.component.ts b/src/main/webapp/app/exercises/programming/manage/update/update-components/custom-build-plans/programming-exercise-custom-build-plan.component.ts index 63b22457fe92..d98a3ff8d01d 100644 --- a/src/main/webapp/app/exercises/programming/manage/update/update-components/custom-build-plans/programming-exercise-custom-build-plan.component.ts +++ b/src/main/webapp/app/exercises/programming/manage/update/update-components/custom-build-plans/programming-exercise-custom-build-plan.component.ts @@ -22,7 +22,6 @@ export class ProgrammingExerciseCustomBuildPlanComponent implements OnChanges { projectType?: ProjectType; staticCodeAnalysisEnabled?: boolean; sequentialTestRuns?: boolean; - testwiseCoverageEnabled?: boolean; isImportFromFile: boolean = false; constructor(private aeolusService: AeolusService) {} @@ -56,17 +55,7 @@ export class ProgrammingExerciseCustomBuildPlanComponent implements OnChanges { (this.programmingExercise.programmingLanguage !== this.programmingLanguage || this.programmingExercise.projectType !== this.projectType || this.programmingExercise.staticCodeAnalysisEnabled !== this.staticCodeAnalysisEnabled || - this.programmingExercise.buildConfig!.sequentialTestRuns !== this.sequentialTestRuns || - this.programmingExercise.buildConfig!.testwiseCoverageEnabled !== this.testwiseCoverageEnabled) - ); - } - - shouldReplacePlaceholders(): boolean { - return ( - (!!this.programmingExercise.buildConfig?.assignmentCheckoutPath && this.programmingExercise.buildConfig?.assignmentCheckoutPath.trim() !== '') || - (!!this.programmingExercise.buildConfig?.testCheckoutPath && this.programmingExercise.buildConfig?.testCheckoutPath.trim() !== '') || - !!this.programmingExercise.buildConfig?.buildScript?.includes('${studentParentWorkingDirectoryName}') || - !!this.programmingExercise.buildConfig?.buildScript?.includes('${testWorkingDirectory}') + this.programmingExercise.buildConfig!.sequentialTestRuns !== this.sequentialTestRuns) ); } @@ -94,37 +83,32 @@ export class ProgrammingExerciseCustomBuildPlanComponent implements OnChanges { this.projectType = this.programmingExercise.projectType; this.staticCodeAnalysisEnabled = this.programmingExercise.staticCodeAnalysisEnabled; this.sequentialTestRuns = this.programmingExercise.buildConfig?.sequentialTestRuns; - this.testwiseCoverageEnabled = this.programmingExercise.buildConfig?.testwiseCoverageEnabled; this.isImportFromFile = isImportFromFile; if (!isImportFromFile || !this.programmingExercise.buildConfig?.windfile) { - this.aeolusService - .getAeolusTemplateFile(this.programmingLanguage, this.projectType, this.staticCodeAnalysisEnabled, this.sequentialTestRuns, this.testwiseCoverageEnabled) - .subscribe({ - next: (file) => { - this.programmingExercise.buildConfig!.windfile = this.aeolusService.parseWindFile(file); - }, - error: () => { - this.programmingExercise.buildConfig!.windfile = undefined; - }, - }); + this.aeolusService.getAeolusTemplateFile(this.programmingLanguage, this.projectType, this.staticCodeAnalysisEnabled, this.sequentialTestRuns).subscribe({ + next: (file) => { + this.programmingExercise.buildConfig!.windfile = this.aeolusService.parseWindFile(file); + }, + error: () => { + this.programmingExercise.buildConfig!.windfile = undefined; + }, + }); } this.programmingExerciseCreationConfig.buildPlanLoaded = true; if (!this.programmingExercise.buildConfig?.windfile) { this.resetCustomBuildPlan(); } if (!isImportFromFile || !this.programmingExercise.buildConfig?.buildScript) { - this.aeolusService - .getAeolusTemplateScript(this.programmingLanguage, this.projectType, this.staticCodeAnalysisEnabled, this.sequentialTestRuns, this.testwiseCoverageEnabled) - .subscribe({ - next: (file: string) => { - file = this.replacePlaceholders(file); - this.codeChanged(file); - this.editor?.setText(file); - }, - error: () => { - this.programmingExercise.buildConfig!.buildScript = undefined; - }, - }); + this.aeolusService.getAeolusTemplateScript(this.programmingLanguage, this.projectType, this.staticCodeAnalysisEnabled, this.sequentialTestRuns).subscribe({ + next: (file: string) => { + file = this.replacePlaceholders(file); + this.codeChanged(file); + this.editor?.setText(file); + }, + error: () => { + this.programmingExercise.buildConfig!.buildScript = undefined; + }, + }); } if (!this.programmingExercise.buildConfig?.buildScript) { this.resetCustomBuildPlan(); diff --git a/src/main/webapp/app/exercises/programming/manage/update/update-components/language/programming-exercise-language.component.html b/src/main/webapp/app/exercises/programming/manage/update/update-components/language/programming-exercise-language.component.html index 4dd22d1e9b35..f5b014871d1c 100644 --- a/src/main/webapp/app/exercises/programming/manage/update/update-components/language/programming-exercise-language.component.html +++ b/src/main/webapp/app/exercises/programming/manage/update/update-components/language/programming-exercise-language.component.html @@ -184,7 +184,7 @@ type="checkbox" name="sequentialTestRuns" id="field_sequentialTestRuns" - [disabled]="!!programmingExercise.id || !!programmingExercise.staticCodeAnalysisEnabled || !!programmingExercise.buildConfig!.testwiseCoverageEnabled" + [disabled]="!!programmingExercise.id || !!programmingExercise.staticCodeAnalysisEnabled" [(ngModel)]="programmingExercise.buildConfig!.sequentialTestRuns" checked /> @@ -195,22 +195,6 @@
    } - - @if (programmingExerciseCreationConfig.testwiseCoverageAnalysisSupported) { -
    - - - -
    - } @if (!programmingExercise.id && programmingExercise.programmingLanguage && programmingExerciseCreationConfig.checkoutSolutionRepositoryAllowed) {