From 062cece2a60f8f514fe76c96d12dd70b961cc9a1 Mon Sep 17 00:00:00 2001 From: Loris Sauter Date: Wed, 11 Oct 2023 11:30:17 +0200 Subject: [PATCH] #337: Added support for import of templates, runs --- .../main/kotlin/dev/dres/api/rest/RestApi.kt | 1 + .../evaluation/admin/ForceViewerHandler.kt | 2 +- .../evaluation/admin/GetEvaluationHandler.kt | 46 +++++++ doc/oas-client.json | 67 ++++++++- doc/oas.json | 129 +++++++++++++++++- .../upload-json-button.component.html | 2 +- .../upload-json-button.component.ts | 4 +- .../template-builder.component.html | 5 +- .../template-builder.component.ts | 48 +++++-- .../template-builder.service.ts | 16 +++ frontend/src/app/utilities/api.utilities.ts | 18 +++ 11 files changed, 312 insertions(+), 26 deletions(-) create mode 100644 backend/src/main/kotlin/dev/dres/api/rest/handler/evaluation/admin/GetEvaluationHandler.kt create mode 100644 frontend/src/app/utilities/api.utilities.ts diff --git a/backend/src/main/kotlin/dev/dres/api/rest/RestApi.kt b/backend/src/main/kotlin/dev/dres/api/rest/RestApi.kt index aac696567..f73a3c354 100644 --- a/backend/src/main/kotlin/dev/dres/api/rest/RestApi.kt +++ b/backend/src/main/kotlin/dev/dres/api/rest/RestApi.kt @@ -195,6 +195,7 @@ object RestApi { ListAllTeamsHandler(store), CreateTeamHandler(store), UpdateTeamHandler(store), + GetEvaluationHandler(store), // Judgement DequeueJudgementHandler(), diff --git a/backend/src/main/kotlin/dev/dres/api/rest/handler/evaluation/admin/ForceViewerHandler.kt b/backend/src/main/kotlin/dev/dres/api/rest/handler/evaluation/admin/ForceViewerHandler.kt index e60880ebf..6178fff65 100644 --- a/backend/src/main/kotlin/dev/dres/api/rest/handler/evaluation/admin/ForceViewerHandler.kt +++ b/backend/src/main/kotlin/dev/dres/api/rest/handler/evaluation/admin/ForceViewerHandler.kt @@ -19,7 +19,7 @@ import jetbrains.exodus.database.TransientEntityStore * @author Loris Sauter * @version 2.0.0 */ -class ForceViewerHandler : AbstractEvaluationAdminHandler(), PostRestHandler { +class ForceViewerHandler() : AbstractEvaluationAdminHandler(), PostRestHandler { override val route: String = "evaluation/admin/{evaluationId}/viewer/list/{viewerId}/force" @OpenApi( diff --git a/backend/src/main/kotlin/dev/dres/api/rest/handler/evaluation/admin/GetEvaluationHandler.kt b/backend/src/main/kotlin/dev/dres/api/rest/handler/evaluation/admin/GetEvaluationHandler.kt new file mode 100644 index 000000000..b6b9ff9d5 --- /dev/null +++ b/backend/src/main/kotlin/dev/dres/api/rest/handler/evaluation/admin/GetEvaluationHandler.kt @@ -0,0 +1,46 @@ +package dev.dres.api.rest.handler.evaluation.admin + +import dev.dres.api.rest.handler.GetRestHandler +import dev.dres.api.rest.types.evaluation.ApiEvaluation +import dev.dres.api.rest.types.evaluation.ApiEvaluationOverview +import dev.dres.api.rest.types.status.ErrorStatus +import dev.dres.api.rest.types.status.ErrorStatusException +import dev.dres.data.model.run.DbEvaluation +import dev.dres.utilities.extensions.evaluationId +import io.javalin.http.Context +import io.javalin.openapi.* +import jetbrains.exodus.database.TransientEntityStore +import kotlinx.dnq.query.eq +import kotlinx.dnq.query.firstOrNull +import kotlinx.dnq.query.query + +class GetEvaluationHandler(private val store: TransientEntityStore): AbstractEvaluationAdminHandler(), + GetRestHandler { + override val route = "evaluation/admin/{evaluationId}" + + @OpenApi( + summary = "Provides the evaluation.", + path = "/api/v2/evaluation/admin/{evaluationId}", + operationId = OpenApiOperation.AUTO_GENERATE, + methods = [HttpMethod.GET], + pathParams = [ + OpenApiParam("evaluationId", String::class, "The evaluation ID", required = true, allowEmptyValue = false), + ], + tags = ["Evaluation Administrator"], + responses = [ + OpenApiResponse("200", [OpenApiContent(ApiEvaluation::class)]), + OpenApiResponse("400", [OpenApiContent(ErrorStatus::class)]), + OpenApiResponse("401", [OpenApiContent(ErrorStatus::class)]), + OpenApiResponse("404", [OpenApiContent(ErrorStatus::class)]) + ] + ) + override fun doGet(ctx: Context): ApiEvaluation { + /* Obtain run id and run. */ + val evaluationId = ctx.pathParamMap().getOrElse("evaluationId") { throw ErrorStatusException(400, "Parameter 'evaluationId' is missing!'", ctx) } + return this.store.transactional(true) { + DbEvaluation.query(DbEvaluation::id eq evaluationId).firstOrNull()?.toApi() + ?: throw ErrorStatusException(404, "Run $evaluationId not found", ctx) + } + + } +} diff --git a/doc/oas-client.json b/doc/oas-client.json index a0df44bb2..250c87086 100644 --- a/doc/oas-client.json +++ b/doc/oas-client.json @@ -1228,6 +1228,43 @@ "type" : "string", "enum" : [ "FRAME_NUMBER", "SECONDS", "MILLISECONDS", "TIMECODE" ] }, + "ApiEvaluation" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "evaluationId" : { + "type" : "string" + }, + "name" : { + "type" : "string" + }, + "type" : { + "$ref" : "#/components/schemas/ApiEvaluationType" + }, + "template" : { + "$ref" : "#/components/schemas/ApiEvaluationTemplate" + }, + "created" : { + "type" : "integer", + "format" : "int64" + }, + "started" : { + "type" : "integer", + "format" : "int64" + }, + "ended" : { + "type" : "integer", + "format" : "int64" + }, + "tasks" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ApiTask" + } + } + }, + "required" : [ "evaluationId", "name", "type", "template", "created", "tasks" ] + }, "ApiEvaluationInfo" : { "type" : "object", "additionalProperties" : false, @@ -1351,6 +1388,33 @@ }, "required" : [ "evaluationId", "taskId", "submissions" ] }, + "ApiTask" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "taskId" : { + "type" : "string" + }, + "templateId" : { + "type" : "string" + }, + "started" : { + "type" : "integer", + "format" : "int64" + }, + "ended" : { + "type" : "integer", + "format" : "int64" + }, + "submissions" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ApiAnswerSet" + } + } + }, + "required" : [ "taskId", "templateId", "submissions" ] + }, "ApiTaskOverview" : { "type" : "object", "additionalProperties" : false, @@ -2150,9 +2214,6 @@ "type" : "object", "additionalProperties" : false, "properties" : { - "id" : { - "type" : "string" - }, "name" : { "type" : "string" }, diff --git a/doc/oas.json b/doc/oas.json index e4f2569e8..353647bf0 100644 --- a/doc/oas.json +++ b/doc/oas.json @@ -957,6 +957,68 @@ "security" : [ ] } }, + "/api/v2/evaluation/admin/{evaluationId}" : { + "get" : { + "tags" : [ "Evaluation Administrator" ], + "summary" : "Provides the evaluation.", + "operationId" : "getApiV2EvaluationAdminByEvaluationId", + "parameters" : [ { + "name" : "evaluationId", + "in" : "path", + "description" : "The evaluation ID", + "required" : true, + "deprecated" : false, + "allowEmptyValue" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "OK", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ApiEvaluation" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorStatus" + } + } + } + }, + "401" : { + "description" : "Unauthorized", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorStatus" + } + } + } + }, + "404" : { + "description" : "Not Found", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorStatus" + } + } + } + } + }, + "deprecated" : false, + "security" : [ ] + } + }, "/api/v2/evaluation/admin/{evaluationId}/adjust/{duration}" : { "patch" : { "tags" : [ "Evaluation Administrator" ], @@ -5580,6 +5642,43 @@ "type" : "string", "enum" : [ "FRAME_NUMBER", "SECONDS", "MILLISECONDS", "TIMECODE" ] }, + "ApiEvaluation" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "evaluationId" : { + "type" : "string" + }, + "name" : { + "type" : "string" + }, + "type" : { + "$ref" : "#/components/schemas/ApiEvaluationType" + }, + "template" : { + "$ref" : "#/components/schemas/ApiEvaluationTemplate" + }, + "created" : { + "type" : "integer", + "format" : "int64" + }, + "started" : { + "type" : "integer", + "format" : "int64" + }, + "ended" : { + "type" : "integer", + "format" : "int64" + }, + "tasks" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ApiTask" + } + } + }, + "required" : [ "evaluationId", "name", "type", "template", "created", "tasks" ] + }, "ApiEvaluationInfo" : { "type" : "object", "additionalProperties" : false, @@ -5703,6 +5802,33 @@ }, "required" : [ "evaluationId", "taskId", "submissions" ] }, + "ApiTask" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "taskId" : { + "type" : "string" + }, + "templateId" : { + "type" : "string" + }, + "started" : { + "type" : "integer", + "format" : "int64" + }, + "ended" : { + "type" : "integer", + "format" : "int64" + }, + "submissions" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ApiAnswerSet" + } + } + }, + "required" : [ "taskId", "templateId", "submissions" ] + }, "ApiTaskOverview" : { "type" : "object", "additionalProperties" : false, @@ -6502,9 +6628,6 @@ "type" : "object", "additionalProperties" : false, "properties" : { - "id" : { - "type" : "string" - }, "name" : { "type" : "string" }, diff --git a/frontend/src/app/shared/upload-json-button/upload-json-button.component.html b/frontend/src/app/shared/upload-json-button/upload-json-button.component.html index f4b3b2ce7..82cac937a 100644 --- a/frontend/src/app/shared/upload-json-button/upload-json-button.component.html +++ b/frontend/src/app/shared/upload-json-button/upload-json-button.component.html @@ -2,7 +2,7 @@ cloud_upload {{ name }} - diff --git a/frontend/src/app/shared/upload-json-button/upload-json-button.component.ts b/frontend/src/app/shared/upload-json-button/upload-json-button.component.ts index 9a5b3a490..a222fa31d 100644 --- a/frontend/src/app/shared/upload-json-button/upload-json-button.component.ts +++ b/frontend/src/app/shared/upload-json-button/upload-json-button.component.ts @@ -14,7 +14,7 @@ export class UploadJsonButtonComponent { /** If multi-select files are enabled. Defaults to false (only single file) */ @Input() multi = false; // Currently only single file upload handled /** The handler to process the uploaded thing */ - @Input() handler: (contents: string) => void; + @Input() handler: (contents: any) => void; @ViewChild('fileInput', { static: false }) fileUpload: HTMLInputElement; @@ -34,7 +34,7 @@ export class UploadJsonButtonComponent { const reader = new FileReader(); reader.onload = (e: any) => { - this.handler(e.target.result); + this.handler(JSON.parse(e.target.result)); }; if (this.multi) { // TODO multi file upload diff --git a/frontend/src/app/template/template-builder/template-builder.component.html b/frontend/src/app/template/template-builder/template-builder.component.html index a83f8ac58..030f85af3 100644 --- a/frontend/src/app/template/template-builder/template-builder.component.html +++ b/frontend/src/app/template/template-builder/template-builder.component.html @@ -30,7 +30,10 @@

Edit evaluation template {{(builderService.templateAsObservable() | async)?. matTooltip="Download the entire evaluation template as JSON">
- +