diff --git a/backend/src/main/kotlin/dev/dres/DRES.kt b/backend/src/main/kotlin/dev/dres/DRES.kt index da3f9078..ed9150a0 100644 --- a/backend/src/main/kotlin/dev/dres/DRES.kt +++ b/backend/src/main/kotlin/dev/dres/DRES.kt @@ -42,7 +42,7 @@ import kotlin.system.exitProcess */ object DRES { /** Version of DRES. */ - const val VERSION = "2.0.0-RC3" + const val VERSION = "2.0.0-RC4" /** Application root; should be relative to JAR file or classes path. */ val APPLICATION_ROOT: Path = diff --git a/backend/src/main/kotlin/dev/dres/api/rest/KotlinxJsonMapper.kt b/backend/src/main/kotlin/dev/dres/api/rest/KotlinxJsonMapper.kt index 628f23c3..81510fb4 100644 --- a/backend/src/main/kotlin/dev/dres/api/rest/KotlinxJsonMapper.kt +++ b/backend/src/main/kotlin/dev/dres/api/rest/KotlinxJsonMapper.kt @@ -1,6 +1,6 @@ package dev.dres.api.rest -import com.fasterxml.jackson.core.type.TypeReference + import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import io.javalin.json.JsonMapper import kotlinx.serialization.KSerializer @@ -12,13 +12,16 @@ import java.lang.reflect.Type object KotlinxJsonMapper : JsonMapper { private val fallbackMapper = jacksonObjectMapper() + private val mapper = Json{ + ignoreUnknownKeys = true + } override fun fromJsonString(json: String, targetType: Type): T { return try { @Suppress("UNCHECKED_CAST") val serializer = serializer(targetType) as KSerializer - Json.decodeFromString(serializer, json) + mapper.decodeFromString(serializer, json) } catch (e: SerializationException) { null } catch (e: IllegalStateException) { @@ -31,7 +34,7 @@ object KotlinxJsonMapper : JsonMapper { return try { val serializer = serializer(type) - Json.encodeToString(serializer, obj) + mapper.encodeToString(serializer, obj) } catch (e: SerializationException) { null } catch (e: IllegalStateException) { diff --git a/backend/src/main/kotlin/dev/dres/api/rest/handler/evaluation/client/ClientTaskInfoHandler.kt b/backend/src/main/kotlin/dev/dres/api/rest/handler/evaluation/client/ClientTaskInfoHandler.kt index 441a87f8..19c3bea5 100644 --- a/backend/src/main/kotlin/dev/dres/api/rest/handler/evaluation/client/ClientTaskInfoHandler.kt +++ b/backend/src/main/kotlin/dev/dres/api/rest/handler/evaluation/client/ClientTaskInfoHandler.kt @@ -1,6 +1,7 @@ package dev.dres.api.rest.handler.evaluation.client import dev.dres.api.rest.handler.GetRestHandler +import dev.dres.api.rest.types.evaluation.ApiClientTaskTemplateInfo import dev.dres.utilities.extensions.eligibleManagerForId import dev.dres.api.rest.types.evaluation.ApiTaskTemplateInfo import dev.dres.api.rest.types.status.ErrorStatus @@ -21,7 +22,7 @@ import jetbrains.exodus.database.TransientEntityStore * @version 2.0.0 */ class ClientTaskInfoHandler : AbstractEvaluationClientHandler(), - GetRestHandler { + GetRestHandler { override val route = "client/evaluation/currentTask/{evaluationId}" @OpenApi( @@ -36,13 +37,13 @@ class ClientTaskInfoHandler : AbstractEvaluationClientHandler(), OpenApiParam("session", String::class, "Session Token") ], responses = [ - OpenApiResponse("200", [OpenApiContent(ApiTaskTemplateInfo::class)]), + OpenApiResponse("200", [OpenApiContent(ApiClientTaskTemplateInfo::class)]), OpenApiResponse("401", [OpenApiContent(ErrorStatus::class)]), OpenApiResponse("404", [OpenApiContent(ErrorStatus::class)]) ], methods = [HttpMethod.GET] ) - override fun doGet(ctx: Context): ApiTaskTemplateInfo { + override fun doGet(ctx: Context): ApiClientTaskTemplateInfo { val run = ctx.eligibleManagerForId() val rac = ctx.runActionContext() if (run !is InteractiveRunManager) throw ErrorStatusException( @@ -52,6 +53,6 @@ class ClientTaskInfoHandler : AbstractEvaluationClientHandler(), ) val task = run.currentTask(rac) ?: throw ErrorStatusException(404, "Specified evaluation has no active task.", ctx) - return ApiTaskTemplateInfo(task.template) + return ApiClientTaskTemplateInfo(task.template) } } diff --git a/backend/src/main/kotlin/dev/dres/api/rest/handler/log/QueryLogHandler.kt b/backend/src/main/kotlin/dev/dres/api/rest/handler/log/QueryLogHandler.kt index 6a631056..f47b7694 100644 --- a/backend/src/main/kotlin/dev/dres/api/rest/handler/log/QueryLogHandler.kt +++ b/backend/src/main/kotlin/dev/dres/api/rest/handler/log/QueryLogHandler.kt @@ -4,10 +4,12 @@ import dev.dres.api.rest.types.status.ErrorStatus import dev.dres.api.rest.types.status.ErrorStatusException import dev.dres.api.rest.types.status.SuccessStatus import dev.dres.data.model.log.QueryEventLog +import dev.dres.run.RunManager import dev.dres.run.eventstream.EventStreamProcessor import dev.dres.run.eventstream.InvalidRequestEvent import dev.dres.run.eventstream.QueryEventLogEvent import dev.dres.utilities.extensions.activeManagerForUser +import dev.dres.utilities.extensions.eligibleManagerForId import dev.dres.utilities.extensions.sessionToken import io.javalin.http.BadRequestResponse import io.javalin.http.Context @@ -22,14 +24,17 @@ import io.javalin.openapi.* * @version 2.0.0 */ class QueryLogHandler : AbstractLogHandler() { - override val route = "log/query" + override val route = "log/query/{evaluationId}" - @OpenApi(summary = "Accepts query logs from participants", - path = "/api/v2/log/query", + @OpenApi(summary = "Accepts query logs from participants for the specified evaluation.", + path = "/api/v2/log/query/{evaluationId}", operationId = OpenApiOperation.AUTO_GENERATE, methods = [HttpMethod.POST], requestBody = OpenApiRequestBody([OpenApiContent(QueryEventLog::class)]), tags = ["Log"], + pathParams = [ + OpenApiParam("evaluationId", String::class, "The evaluation ID.", required = true, allowEmptyValue = false) + ], queryParams = [ OpenApiParam("session", String::class, "Session Token", required = true, allowEmptyValue = false) ], @@ -39,7 +44,8 @@ class QueryLogHandler : AbstractLogHandler() { OpenApiResponse("401", [OpenApiContent(ErrorStatus::class)])] ) override fun doPost(ctx: Context): SuccessStatus { - val evaluationManager = ctx.activeManagerForUser() + val evaluationManager = ctx.eligibleManagerForId() + // val evaluationManager = ctx.activeManagerForUser() val queryEventLog = try { ctx.bodyAsClass() } catch (e: BadRequestResponse){ diff --git a/backend/src/main/kotlin/dev/dres/api/rest/handler/log/ResultLogHandler.kt b/backend/src/main/kotlin/dev/dres/api/rest/handler/log/ResultLogHandler.kt index 9e88ebeb..0ac40f0d 100644 --- a/backend/src/main/kotlin/dev/dres/api/rest/handler/log/ResultLogHandler.kt +++ b/backend/src/main/kotlin/dev/dres/api/rest/handler/log/ResultLogHandler.kt @@ -5,10 +5,12 @@ import dev.dres.api.rest.types.status.ErrorStatus import dev.dres.api.rest.types.status.ErrorStatusException import dev.dres.api.rest.types.status.SuccessStatus import dev.dres.data.model.log.QueryResultLog +import dev.dres.run.RunManager import dev.dres.run.eventstream.EventStreamProcessor import dev.dres.run.eventstream.InvalidRequestEvent import dev.dres.run.eventstream.QueryResultLogEvent import dev.dres.utilities.extensions.activeManagerForUser +import dev.dres.utilities.extensions.eligibleManagerForId import dev.dres.utilities.extensions.sessionToken import io.javalin.http.BadRequestResponse import io.javalin.http.Context @@ -22,14 +24,17 @@ import io.javalin.openapi.* * @version 2.0.0 */ class ResultLogHandler: AbstractLogHandler() { - override val route = "log/result" + override val route = "log/result/{evaluationId}" - @OpenApi(summary = "Accepts result logs from participants.", - path = "/api/v2/log/result", + @OpenApi(summary = "Accepts result logs from participants for the specified evaluation.", + path = "/api/v2/log/result/{evaluationId}", operationId = OpenApiOperation.AUTO_GENERATE, methods = [HttpMethod.POST], requestBody = OpenApiRequestBody([OpenApiContent(QueryResultLog::class)]), tags = ["Log"], + pathParams = [ + OpenApiParam("evaluationId", String::class, "The evaluation ID.", required = true, allowEmptyValue = false) + ], queryParams = [ OpenApiParam("session", String::class, "Session Token", required = true, allowEmptyValue = false) ], @@ -39,7 +44,8 @@ class ResultLogHandler: AbstractLogHandler() { OpenApiResponse("401", [OpenApiContent(ErrorStatus::class)])] ) override fun doPost(ctx: Context): SuccessStatus { - val evaluationManager = ctx.activeManagerForUser() + // val evaluationManager = ctx.activeManagerForUser() + val evaluationManager = ctx.eligibleManagerForId() val queryResultLog = try { ctx.bodyAsClass(QueryResultLog::class.java) } catch (e: BadRequestResponse){ diff --git a/backend/src/main/kotlin/dev/dres/api/rest/types/evaluation/submission/ApiAnswerSet.kt b/backend/src/main/kotlin/dev/dres/api/rest/types/evaluation/submission/ApiAnswerSet.kt index 9c0324d7..90a4b69f 100644 --- a/backend/src/main/kotlin/dev/dres/api/rest/types/evaluation/submission/ApiAnswerSet.kt +++ b/backend/src/main/kotlin/dev/dres/api/rest/types/evaluation/submission/ApiAnswerSet.kt @@ -25,6 +25,7 @@ data class ApiAnswerSet( @get:JsonIgnore @get:OpenApiIgnore + @kotlinx.serialization.Transient override lateinit var submission: Submission @JsonIgnore @OpenApiIgnore diff --git a/backend/src/main/kotlin/dev/dres/api/rest/types/evaluation/submission/ApiClientAnswerSet.kt b/backend/src/main/kotlin/dev/dres/api/rest/types/evaluation/submission/ApiClientAnswerSet.kt index b91149b6..c3a9ad7d 100644 --- a/backend/src/main/kotlin/dev/dres/api/rest/types/evaluation/submission/ApiClientAnswerSet.kt +++ b/backend/src/main/kotlin/dev/dres/api/rest/types/evaluation/submission/ApiClientAnswerSet.kt @@ -26,6 +26,7 @@ data class ApiClientAnswerSet(var taskId: String? = null, val taskName: String? /** The [AnswerSetId] of this [ApiClientAnswerSet]. Typically generated by the receiving endpoint. */ @JsonIgnore @get:OpenApiIgnore + @kotlinx.serialization.Transient val answerSetId: AnswerSetId = UUID.randomUUID().toString() /** diff --git a/backend/src/main/kotlin/dev/dres/api/rest/types/evaluation/submission/ApiClientSubmission.kt b/backend/src/main/kotlin/dev/dres/api/rest/types/evaluation/submission/ApiClientSubmission.kt index 0b2ab1bb..53e12349 100644 --- a/backend/src/main/kotlin/dev/dres/api/rest/types/evaluation/submission/ApiClientSubmission.kt +++ b/backend/src/main/kotlin/dev/dres/api/rest/types/evaluation/submission/ApiClientSubmission.kt @@ -31,22 +31,26 @@ data class ApiClientSubmission( @JsonIgnore @get:OpenApiIgnore @set:OpenApiIgnore + @kotlinx.serialization.Transient var userId: UserId? = null, /** The [TeamId] associated with the submission. Is usually added as contextual information by the receiving endpoint. */ @JsonIgnore @get:OpenApiIgnore @set:OpenApiIgnore + @kotlinx.serialization.Transient var teamId: TeamId? = null, /** The [SubmissionId] of this [ApiClientSubmission]. Typically generated by the receiving endpoint. */ @JsonIgnore @get:OpenApiIgnore + @kotlinx.serialization.Transient val submissionId: SubmissionId = UUID.randomUUID().toString(), /** The timestamp at which this [ApiClientSubmission] was received. Typically generated by the receiving endpoint.*/ @JsonIgnore @get:OpenApiIgnore + @kotlinx.serialization.Transient val timestamp: Long = System.currentTimeMillis() ) { diff --git a/backend/src/main/kotlin/dev/dres/api/rest/types/template/ApiEvaluationTemplate.kt b/backend/src/main/kotlin/dev/dres/api/rest/types/template/ApiEvaluationTemplate.kt index 951bf128..14b8b77b 100644 --- a/backend/src/main/kotlin/dev/dres/api/rest/types/template/ApiEvaluationTemplate.kt +++ b/backend/src/main/kotlin/dev/dres/api/rest/types/template/ApiEvaluationTemplate.kt @@ -37,6 +37,7 @@ data class ApiEvaluationTemplate( @get:JsonIgnore @delegate:JsonIgnore @get:OpenApiIgnore + //@kotlinx.serialization.Transient not needed due to delegation private val allParticipantIds by lazy { teams.flatMap { team -> team.users.mapNotNull { it.id } }.toSet() } @JsonIgnore diff --git a/backend/src/main/kotlin/dev/dres/data/model/log/ParticipantLog.kt b/backend/src/main/kotlin/dev/dres/data/model/log/ParticipantLog.kt index cfcbd27d..fb1154df 100644 --- a/backend/src/main/kotlin/dev/dres/data/model/log/ParticipantLog.kt +++ b/backend/src/main/kotlin/dev/dres/data/model/log/ParticipantLog.kt @@ -4,13 +4,16 @@ import com.fasterxml.jackson.annotation.JsonIgnore import com.fasterxml.jackson.annotation.JsonIgnoreProperties import com.fasterxml.jackson.annotation.JsonSetter import com.fasterxml.jackson.annotation.Nulls +import dev.dres.api.rest.types.evaluation.submission.ApiClientAnswer import io.javalin.openapi.OpenApiIgnore +import kotlinx.serialization.Serializable enum class QueryEventCategory { TEXT, IMAGE, SKETCH, FILTER, BROWSING, COOPERATION, OTHER } @JsonIgnoreProperties(ignoreUnknown = true) +@Serializable data class QueryEvent( val timestamp: Long = -1, val category: QueryEventCategory = QueryEventCategory.OTHER, @@ -19,6 +22,7 @@ data class QueryEvent( ) @JsonIgnoreProperties(ignoreUnknown = true) +@Serializable data class QueryEventLog internal constructor( val timestamp: Long = -1, @field:JsonSetter(contentNulls = Nulls.FAIL) @@ -26,29 +30,30 @@ data class QueryEventLog internal constructor( @field:JsonIgnore @get:JsonIgnore @get:OpenApiIgnore + @kotlinx.serialization.Transient internal val serverTimeStamp: Long = System.currentTimeMillis() ) @JsonIgnoreProperties(ignoreUnknown = true) -data class QueryResult( - val item: String = "", - val segment: Int? = null, - val frame: Int? = null, - val score: Double? = null, +@Serializable +data class RankedAnswer( + val answer: ApiClientAnswer, val rank: Int? = null ) @JsonIgnoreProperties(ignoreUnknown = true) +@Serializable data class QueryResultLog internal constructor( val timestamp: Long = -1, val sortType: String = "", val resultSetAvailability: String = "", @field:JsonSetter(contentNulls = Nulls.FAIL) - val results: List = emptyList(), + val results: List = emptyList(), @field:JsonSetter(contentNulls = Nulls.FAIL) val events: List = emptyList(), @field:JsonIgnore @get:JsonIgnore @get:OpenApiIgnore + @kotlinx.serialization.Transient internal val serverTimeStamp: Long = System.currentTimeMillis() ) \ No newline at end of file diff --git a/doc/oas-client.json b/doc/oas-client.json index bfa19c4f..4083d741 100644 --- a/doc/oas-client.json +++ b/doc/oas-client.json @@ -2,8 +2,8 @@ "openapi" : "3.0.3", "info" : { "title" : "DRES Client API", - "description" : "Client API for DRES (Distributed Retrieval Evaluation Server), Version 2.0.0-RC3", - "version" : "2.0.0-RC3" + "description" : "Client API for DRES (Distributed Retrieval Evaluation Server), Version 2.0.0-RC4", + "version" : "2.0.0-RC4" }, "paths" : { "/api/v1/submit" : { @@ -183,7 +183,7 @@ "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/ApiTaskTemplateInfo" + "$ref" : "#/components/schemas/ApiClientTaskTemplateInfo" } } } @@ -452,12 +452,22 @@ "security" : [ ] } }, - "/api/v2/log/query" : { + "/api/v2/log/query/{evaluationId}" : { "post" : { "tags" : [ "Log" ], - "summary" : "Accepts query logs from participants", - "operationId" : "postApiV2LogQuery", + "summary" : "Accepts query logs from participants for the specified evaluation.", + "operationId" : "postApiV2LogQueryByEvaluationId", "parameters" : [ { + "name" : "evaluationId", + "in" : "path", + "description" : "The evaluation ID.", + "required" : true, + "deprecated" : false, + "allowEmptyValue" : false, + "schema" : { + "type" : "string" + } + }, { "name" : "session", "in" : "query", "description" : "Session Token", @@ -514,12 +524,22 @@ "security" : [ ] } }, - "/api/v2/log/result" : { + "/api/v2/log/result/{evaluationId}" : { "post" : { "tags" : [ "Log" ], - "summary" : "Accepts result logs from participants.", - "operationId" : "postApiV2LogResult", + "summary" : "Accepts result logs from participants for the specified evaluation.", + "operationId" : "postApiV2LogResultByEvaluationId", "parameters" : [ { + "name" : "evaluationId", + "in" : "path", + "description" : "The evaluation ID.", + "required" : true, + "deprecated" : false, + "allowEmptyValue" : false, + "schema" : { + "type" : "string" + } + }, { "name" : "session", "in" : "query", "description" : "Session Token", @@ -2467,32 +2487,6 @@ }, "required" : [ "timestamp", "events" ] }, - "QueryResult" : { - "type" : "object", - "additionalProperties" : false, - "properties" : { - "item" : { - "type" : "string" - }, - "segment" : { - "type" : "integer", - "format" : "int32" - }, - "frame" : { - "type" : "integer", - "format" : "int32" - }, - "score" : { - "type" : "number", - "format" : "double" - }, - "rank" : { - "type" : "integer", - "format" : "int32" - } - }, - "required" : [ "item" ] - }, "QueryResultLog" : { "type" : "object", "additionalProperties" : false, @@ -2510,7 +2504,7 @@ "results" : { "type" : "array", "items" : { - "$ref" : "#/components/schemas/QueryResult" + "$ref" : "#/components/schemas/RankedAnswer" } }, "events" : { @@ -2522,6 +2516,20 @@ }, "required" : [ "timestamp", "sortType", "resultSetAvailability", "results", "events" ] }, + "RankedAnswer" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "answer" : { + "$ref" : "#/components/schemas/ApiClientAnswer" + }, + "rank" : { + "type" : "integer", + "format" : "int32" + } + }, + "required" : [ "answer" ] + }, "TemporalPoint" : { "type" : "object", "additionalProperties" : false, diff --git a/doc/oas.json b/doc/oas.json index 25314304..a43f6c1f 100644 --- a/doc/oas.json +++ b/doc/oas.json @@ -2,7 +2,7 @@ "openapi" : "3.0.3", "info" : { "title" : "DRES API", - "description" : "API for DRES (Distributed Retrieval Evaluation Server), Version 2.0.0-RC3", + "description" : "API for DRES (Distributed Retrieval Evaluation Server), Version 2.0.0-RC4", "contact" : { "name" : "The DRES Dev Team", "url" : "https://dres.dev" @@ -10,7 +10,7 @@ "license" : { "name" : "MIT" }, - "version" : "2.0.0-RC3" + "version" : "2.0.0-RC4" }, "paths" : { "/api/v1/submit" : { @@ -190,7 +190,7 @@ "content" : { "application/json" : { "schema" : { - "$ref" : "#/components/schemas/ApiTaskTemplateInfo" + "$ref" : "#/components/schemas/ApiClientTaskTemplateInfo" } } } @@ -3348,12 +3348,22 @@ "security" : [ ] } }, - "/api/v2/log/query" : { + "/api/v2/log/query/{evaluationId}" : { "post" : { "tags" : [ "Log" ], - "summary" : "Accepts query logs from participants", - "operationId" : "postApiV2LogQuery", + "summary" : "Accepts query logs from participants for the specified evaluation.", + "operationId" : "postApiV2LogQueryByEvaluationId", "parameters" : [ { + "name" : "evaluationId", + "in" : "path", + "description" : "The evaluation ID.", + "required" : true, + "deprecated" : false, + "allowEmptyValue" : false, + "schema" : { + "type" : "string" + } + }, { "name" : "session", "in" : "query", "description" : "Session Token", @@ -3410,12 +3420,22 @@ "security" : [ ] } }, - "/api/v2/log/result" : { + "/api/v2/log/result/{evaluationId}" : { "post" : { "tags" : [ "Log" ], - "summary" : "Accepts result logs from participants.", - "operationId" : "postApiV2LogResult", + "summary" : "Accepts result logs from participants for the specified evaluation.", + "operationId" : "postApiV2LogResultByEvaluationId", "parameters" : [ { + "name" : "evaluationId", + "in" : "path", + "description" : "The evaluation ID.", + "required" : true, + "deprecated" : false, + "allowEmptyValue" : false, + "schema" : { + "type" : "string" + } + }, { "name" : "session", "in" : "query", "description" : "Session Token", @@ -6881,32 +6901,6 @@ }, "required" : [ "timestamp", "events" ] }, - "QueryResult" : { - "type" : "object", - "additionalProperties" : false, - "properties" : { - "item" : { - "type" : "string" - }, - "segment" : { - "type" : "integer", - "format" : "int32" - }, - "frame" : { - "type" : "integer", - "format" : "int32" - }, - "score" : { - "type" : "number", - "format" : "double" - }, - "rank" : { - "type" : "integer", - "format" : "int32" - } - }, - "required" : [ "item" ] - }, "QueryResultLog" : { "type" : "object", "additionalProperties" : false, @@ -6924,7 +6918,7 @@ "results" : { "type" : "array", "items" : { - "$ref" : "#/components/schemas/QueryResult" + "$ref" : "#/components/schemas/RankedAnswer" } }, "events" : { @@ -6936,6 +6930,20 @@ }, "required" : [ "timestamp", "sortType", "resultSetAvailability", "results", "events" ] }, + "RankedAnswer" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "answer" : { + "$ref" : "#/components/schemas/ApiClientAnswer" + }, + "rank" : { + "type" : "integer", + "format" : "int32" + } + }, + "required" : [ "answer" ] + }, "TemporalPoint" : { "type" : "object", "additionalProperties" : false,