From 4a034cb3b6ea84ea885d92525771602883716c47 Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Thu, 8 Sep 2022 09:31:57 +0300 Subject: [PATCH] Create `LnkContestExecution` whenever a new execution is submitted under a contest (#1182) * Create `LnkContestExecution` whenever a new execution is submitted under a contest * Move `testingType` from request params to `RunExecutionRequest` * Pass `contestName` if `testingType` is `CONTEST_MODE` * Check if the project is enrolled into a contest when the execution request is submitted * Support specifying contest name in save-api --- .../saveourtool/save/apicli/ArgsParsing.kt | 18 ++++++++- .../com/saveourtool/save/apicli/Main.kt | 3 +- .../saveourtool/save/api/SaveCloudClient.kt | 10 +++-- .../save/api/utils/RequestUtils.kt | 6 +-- save-backend/backend-api-docs.json | 29 +++++++------- .../controllers/RunExecutionController.kt | 24 +++++++++--- .../repository/LnkContestProjectRepository.kt | 12 ++++++ .../save/backend/service/ExecutionService.kt | 38 ++++++++++++++----- .../service/LnkContestExecutionService.kt | 21 ++++++++++ .../service/LnkContestProjectService.kt | 10 +++++ .../controllers/RunExecutionControllerTest.kt | 1 + .../com/saveourtool/save/entities/Project.kt | 2 +- .../save/entities/RunExecutionRequest.kt | 14 ++++++- .../saveourtool/save/utils/ReactorUtils.kt | 16 ++++++++ .../frontend/components/views/ProjectView.kt | 6 ++- 15 files changed, 166 insertions(+), 44 deletions(-) diff --git a/save-api-cli/src/main/kotlin/com/saveourtool/save/apicli/ArgsParsing.kt b/save-api-cli/src/main/kotlin/com/saveourtool/save/apicli/ArgsParsing.kt index 872b54c69b..94340da1ab 100644 --- a/save-api-cli/src/main/kotlin/com/saveourtool/save/apicli/ArgsParsing.kt +++ b/save-api-cli/src/main/kotlin/com/saveourtool/save/apicli/ArgsParsing.kt @@ -13,16 +13,19 @@ import org.slf4j.LoggerFactory import kotlinx.cli.ArgParser import kotlinx.cli.ArgType +import kotlinx.cli.required private val log = LoggerFactory.getLogger(object {}.javaClass.enclosingClass::class.java) /** * @property authorization authorization data - * @property mode mode of execution: git/standard + * @property mode mode of execution (one of [TestingType]) + * @property contestName name of the contest in which the tool participates */ data class CliArguments( val authorization: Authorization, val mode: TestingType, + val contestName: String? = null, ) /** @@ -58,6 +61,7 @@ fun parseArguments(args: Array): CliArguments? { shortName = "t", description = "OAuth token for SAVE-cloud system" ) + .required() val mode by parser.option( ArgType.Choice(), @@ -65,6 +69,15 @@ fun parseArguments(args: Array): CliArguments? { shortName = "m", description = "Mode of execution: git/standard" ) + .required() + + val contestName by parser.option( + ArgType.String, + fullName = "contest-name", + shortName = "cn", + description = "Name of the contest that this tool participates in", + ) + parser.parse(args) val authorization = oauth2Source?.let { @@ -73,6 +86,7 @@ fun parseArguments(args: Array): CliArguments? { return CliArguments( authorization, - mode!! + mode, + contestName, ) } diff --git a/save-api-cli/src/main/kotlin/com/saveourtool/save/apicli/Main.kt b/save-api-cli/src/main/kotlin/com/saveourtool/save/apicli/Main.kt index 3b8c0ea343..6efe6785c4 100644 --- a/save-api-cli/src/main/kotlin/com/saveourtool/save/apicli/Main.kt +++ b/save-api-cli/src/main/kotlin/com/saveourtool/save/apicli/Main.kt @@ -32,7 +32,8 @@ fun main(args: Array) { webClientProperties, evaluatedToolProperties, cliArgs.mode, - cliArgs.authorization + cliArgs.contestName, + cliArgs.authorization, ) runBlocking { diff --git a/save-api/src/main/kotlin/com/saveourtool/save/api/SaveCloudClient.kt b/save-api/src/main/kotlin/com/saveourtool/save/api/SaveCloudClient.kt index de642ab62f..b62d50cfc4 100644 --- a/save-api/src/main/kotlin/com/saveourtool/save/api/SaveCloudClient.kt +++ b/save-api/src/main/kotlin/com/saveourtool/save/api/SaveCloudClient.kt @@ -35,6 +35,7 @@ class SaveCloudClient( webClientProperties: WebClientProperties, private val evaluatedToolProperties: EvaluatedToolProperties, private val testingType: TestingType, + private val contestName: String?, authorization: Authorization, ) { private val log = LoggerFactory.getLogger(SaveCloudClient::class.java) @@ -63,7 +64,7 @@ class SaveCloudClient( } log.info("Starting submit execution $msg, type: $testingType") - val executionRequest = submitExecution(additionalFileInfoList) ?: return + val executionRequest = submitExecution(additionalFileInfoList, contestName) ?: return // Sending requests, which checks current state, until results will be received // TODO: in which form do we actually need results? @@ -83,7 +84,8 @@ class SaveCloudClient( * @return pair of organization and submitted execution request */ private suspend fun submitExecution( - additionalFiles: List? + additionalFiles: List?, + contestName: String?, ): RunExecutionRequest? { val runExecutionRequest = RunExecutionRequest( projectCoordinates = ProjectCoordinates( @@ -97,8 +99,10 @@ class SaveCloudClient( sdk = evaluatedToolProperties.sdk.toSdk(), execCmd = evaluatedToolProperties.execCmd, batchSizeForAnalyzer = evaluatedToolProperties.batchSize, + testingType = testingType, + contestName = contestName, ) - val response = httpClient.submitExecution(testingType, runExecutionRequest) + val response = httpClient.submitExecution(runExecutionRequest) if (response.status != HttpStatusCode.OK && response.status != HttpStatusCode.Accepted) { log.error("Can't submit execution=$runExecutionRequest! Response status: ${response.status}") return null diff --git a/save-api/src/main/kotlin/com/saveourtool/save/api/utils/RequestUtils.kt b/save-api/src/main/kotlin/com/saveourtool/save/api/utils/RequestUtils.kt index 7fa4213269..cf55cea7cb 100644 --- a/save-api/src/main/kotlin/com/saveourtool/save/api/utils/RequestUtils.kt +++ b/save-api/src/main/kotlin/com/saveourtool/save/api/utils/RequestUtils.kt @@ -10,7 +10,6 @@ import com.saveourtool.save.domain.FileInfo import com.saveourtool.save.domain.ShortFileInfo import com.saveourtool.save.entities.RunExecutionRequest import com.saveourtool.save.execution.ExecutionDto -import com.saveourtool.save.execution.TestingType import com.saveourtool.save.utils.LocalDateTimeSerializer import com.saveourtool.save.utils.extractUserNameAndSource import com.saveourtool.save.v1 @@ -87,13 +86,12 @@ suspend fun HttpClient.uploadAdditionalFile( /** * Submit execution * - * @param testingType type of requested execution [TestingType] * @param runExecutionRequest execution request * @return HttpResponse */ @Suppress("TOO_LONG_FUNCTION") -suspend fun HttpClient.submitExecution(testingType: TestingType, runExecutionRequest: RunExecutionRequest): HttpResponse = this.post { - url("${Backend.url}/api/$v1/run/trigger?testingType=${testingType.name}") +suspend fun HttpClient.submitExecution(runExecutionRequest: RunExecutionRequest): HttpResponse = this.post { + url("${Backend.url}/api/$v1/run/trigger") header("X-Authorization-Source", UserInformation.source) header(HttpHeaders.ContentType, ContentType.Application.Json) setBody(runExecutionRequest) diff --git a/save-backend/backend-api-docs.json b/save-backend/backend-api-docs.json index 66599a3a44..3aa39eb071 100644 --- a/save-backend/backend-api-docs.json +++ b/save-backend/backend-api-docs.json @@ -761,21 +761,6 @@ "run-execution-controller" ], "operationId": "trigger", - "parameters": [ - { - "name": "testingType", - "in": "query", - "required": true, - "schema": { - "type": "string", - "enum": [ - "CONTEST_MODE", - "PRIVATE_TESTS", - "PUBLIC_TESTS" - ] - } - } - ], "requestBody": { "content": { "application/json": { @@ -6862,7 +6847,8 @@ "files", "projectCoordinates", "sdk", - "testSuiteIds" + "testSuiteIds", + "testingType" ], "type": "object", "properties": { @@ -6890,6 +6876,17 @@ }, "batchSizeForAnalyzer": { "type": "string" + }, + "testingType": { + "type": "string", + "enum": [ + "CONTEST_MODE", + "PRIVATE_TESTS", + "PUBLIC_TESTS" + ] + }, + "contestName": { + "type": "string" } } }, diff --git a/save-backend/src/main/kotlin/com/saveourtool/save/backend/controllers/RunExecutionController.kt b/save-backend/src/main/kotlin/com/saveourtool/save/backend/controllers/RunExecutionController.kt index 321f4aa7df..54946ab353 100644 --- a/save-backend/src/main/kotlin/com/saveourtool/save/backend/controllers/RunExecutionController.kt +++ b/save-backend/src/main/kotlin/com/saveourtool/save/backend/controllers/RunExecutionController.kt @@ -49,6 +49,7 @@ class RunExecutionController( private val executionInfoStorage: ExecutionInfoStorage, private val testService: TestService, private val testExecutionService: TestExecutionService, + private val lnkContestProjectService: LnkContestProjectService, private val meterRegistry: MeterRegistry, configProperties: ConfigProperties, objectMapper: ObjectMapper, @@ -64,17 +65,16 @@ class RunExecutionController( /** * @param request incoming request from frontend * @param authentication - * @param testingType type for this execution * @return response with ID of created [Execution] */ @PostMapping("/trigger") fun trigger( @RequestBody request: RunExecutionRequest, - @RequestParam testingType: TestingType, authentication: Authentication, ): Mono = Mono.just(request.projectCoordinates) .validateAccess(authentication) { it } - .map { + .validateContestEnrollment(request) + .flatMap { executionService.createNew( projectCoordinates = request.projectCoordinates, testSuiteIds = request.testSuiteIds, @@ -83,7 +83,8 @@ class RunExecutionController( sdk = request.sdk, execCmd = request.execCmd, batchSizeForAnalyzer = request.batchSizeForAnalyzer, - testingType = testingType + testingType = request.testingType, + contestName = request.contestName, ) } .subscribeOn(Schedulers.boundedElastic()) @@ -115,7 +116,7 @@ class RunExecutionController( execution.project.name ) } - .map { executionService.createNewCopy(it, authentication.username()) } + .flatMap { executionService.createNewCopy(it, authentication.username()) } .flatMap { execution -> Mono.just(execution.toAcceptedResponse()) .doOnSuccess { @@ -141,6 +142,19 @@ class RunExecutionController( }.map { value } } + @Suppress("UnsafeCallOnNullableType") + private fun Mono.validateContestEnrollment(request: RunExecutionRequest) = + filter { projectCoordinates -> + if (request.testingType == TestingType.CONTEST_MODE) { + lnkContestProjectService.isEnrolled(projectCoordinates, request.contestName!!) + } else { + true + } + } + .switchIfEmptyToResponseException(HttpStatus.CONFLICT) { + "Project ${request.projectCoordinates} isn't enrolled into contest ${request.contestName}" + } + @Suppress("TOO_LONG_FUNCTION") private fun asyncTrigger(execution: Execution) { val executionId = execution.requiredId() diff --git a/save-backend/src/main/kotlin/com/saveourtool/save/backend/repository/LnkContestProjectRepository.kt b/save-backend/src/main/kotlin/com/saveourtool/save/backend/repository/LnkContestProjectRepository.kt index de5fa4c505..30f892ecea 100644 --- a/save-backend/src/main/kotlin/com/saveourtool/save/backend/repository/LnkContestProjectRepository.kt +++ b/save-backend/src/main/kotlin/com/saveourtool/save/backend/repository/LnkContestProjectRepository.kt @@ -47,6 +47,18 @@ interface LnkContestProjectRepository : BaseEntityRepository projectIds: Set, ): List + /** + * @param contestName + * @param organizationName + * @param projectName + * @return a [LnkContestProject] if any has been found + */ + fun findByContestNameAndProjectOrganizationNameAndProjectName( + contestName: String, + organizationName: String, + projectName: String, + ): LnkContestProject? + /** * @param contestName * @return list of [LnkContestProject] linked to contest with name [contestName] diff --git a/save-backend/src/main/kotlin/com/saveourtool/save/backend/service/ExecutionService.kt b/save-backend/src/main/kotlin/com/saveourtool/save/backend/service/ExecutionService.kt index 849a2b4541..7393941cdd 100644 --- a/save-backend/src/main/kotlin/com/saveourtool/save/backend/service/ExecutionService.kt +++ b/save-backend/src/main/kotlin/com/saveourtool/save/backend/service/ExecutionService.kt @@ -8,6 +8,8 @@ import com.saveourtool.save.entities.Organization import com.saveourtool.save.entities.Project import com.saveourtool.save.execution.ExecutionStatus import com.saveourtool.save.execution.TestingType +import com.saveourtool.save.utils.asyncEffectIf +import com.saveourtool.save.utils.blockingToMono import com.saveourtool.save.utils.debug import com.saveourtool.save.utils.orNotFound @@ -17,6 +19,7 @@ import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import org.springframework.web.server.ResponseStatusException +import reactor.core.publisher.Mono import java.time.LocalDateTime import java.util.Optional @@ -35,6 +38,7 @@ class ExecutionService( @Lazy private val testSuitesService: TestSuitesService, private val configProperties: ConfigProperties, private val lnkContestProjectService: LnkContestProjectService, + private val lnkContestExecutionService: LnkContestExecutionService, ) { private val log = LoggerFactory.getLogger(ExecutionService::class.java) @@ -143,6 +147,7 @@ class ExecutionService( * @param execCmd * @param batchSizeForAnalyzer * @param testingType + * @param contestName * @return new [Execution] with provided values */ @Suppress("LongParameterList", "TOO_MANY_PARAMETERS") @@ -155,8 +160,9 @@ class ExecutionService( sdk: Sdk, execCmd: String?, batchSizeForAnalyzer: String?, - testingType: TestingType - ): Execution { + testingType: TestingType, + contestName: String?, + ): Mono { val project = with(projectCoordinates) { projectService.findByNameAndOrganizationName(projectName, organizationName).orNotFound { "Not found project $projectName in $organizationName" @@ -174,7 +180,8 @@ class ExecutionService( sdk = sdk.toString(), execCmd = execCmd, batchSizeForAnalyzer = batchSizeForAnalyzer, - testingType = testingType + testingType = testingType, + contestName, ) } @@ -187,7 +194,7 @@ class ExecutionService( fun createNewCopy( execution: Execution, username: String, - ): Execution = doCreateNew( + ): Mono = doCreateNew( project = execution.project, formattedTestSuiteIds = execution.testSuiteIds, version = execution.version, @@ -198,6 +205,8 @@ class ExecutionService( execCmd = execution.execCmd, batchSizeForAnalyzer = execution.batchSizeForAnalyzer, testingType = execution.type, + contestName = lnkContestExecutionService.takeIf { execution.type == TestingType.CONTEST_MODE } + ?.findContestByExecution(execution)?.name, ) @Suppress("LongParameterList", "TOO_MANY_PARAMETERS", "UnsafeCallOnNullableType") @@ -211,8 +220,9 @@ class ExecutionService( sdk: String, execCmd: String?, batchSizeForAnalyzer: String?, - testingType: TestingType - ): Execution { + testingType: TestingType, + contestName: String?, + ): Mono { val user = userRepository.findByName(username).orNotFound { "Not found user $username" } @@ -245,8 +255,18 @@ class ExecutionService( testSuiteSourceName = testSuiteSourceName, score = null, ) - val savedExecution = saveExecution(execution) - log.info("Created a new execution id=${savedExecution.id} for project id=${project.id}") - return savedExecution + return blockingToMono { + saveExecution(execution) + } + .asyncEffectIf({ testingType == TestingType.CONTEST_MODE }) { savedExecution -> + lnkContestExecutionService.createLink( + savedExecution, requireNotNull(contestName) { + "Requested execution type is ${TestingType.CONTEST_MODE} but no contest name has been specified" + } + ) + } + .doOnSuccess { savedExecution -> + log.info("Created a new execution id=${savedExecution.id} for project id=${project.id}") + } } } diff --git a/save-backend/src/main/kotlin/com/saveourtool/save/backend/service/LnkContestExecutionService.kt b/save-backend/src/main/kotlin/com/saveourtool/save/backend/service/LnkContestExecutionService.kt index 6f13be4079..c5e3e920ae 100644 --- a/save-backend/src/main/kotlin/com/saveourtool/save/backend/service/LnkContestExecutionService.kt +++ b/save-backend/src/main/kotlin/com/saveourtool/save/backend/service/LnkContestExecutionService.kt @@ -1,15 +1,20 @@ package com.saveourtool.save.backend.service +import com.saveourtool.save.backend.repository.ContestRepository import com.saveourtool.save.backend.repository.LnkContestExecutionRepository import com.saveourtool.save.entities.* +import com.saveourtool.save.utils.blockingToMono +import com.saveourtool.save.utils.switchIfEmptyToNotFound import org.springframework.data.domain.PageRequest import org.springframework.stereotype.Service +import reactor.core.publisher.Mono /** * Service of [LnkContestExecution] */ @Service class LnkContestExecutionService( + private val contestRepository: ContestRepository, private val lnkContestExecutionRepository: LnkContestExecutionRepository, ) { /** @@ -49,4 +54,20 @@ class LnkContestExecutionService( * with any [Contest] */ fun findContestByExecution(execution: Execution) = lnkContestExecutionRepository.findByExecution(execution)?.contest + + /** + * @param execution + * @param contestName + * @return [Mono] containing a created [LnkContestExecution] or `Mono.error` with code 404 + */ + fun createLink(execution: Execution, contestName: String) = blockingToMono { + contestRepository.findByName(contestName) + } + .filter { it.isPresent } + .switchIfEmptyToNotFound() + .map { + lnkContestExecutionRepository.save( + LnkContestExecution(execution = execution, contest = it.get()) + ) + } } diff --git a/save-backend/src/main/kotlin/com/saveourtool/save/backend/service/LnkContestProjectService.kt b/save-backend/src/main/kotlin/com/saveourtool/save/backend/service/LnkContestProjectService.kt index 079549f630..6352a52c43 100644 --- a/save-backend/src/main/kotlin/com/saveourtool/save/backend/service/LnkContestProjectService.kt +++ b/save-backend/src/main/kotlin/com/saveourtool/save/backend/service/LnkContestProjectService.kt @@ -1,6 +1,7 @@ package com.saveourtool.save.backend.service import com.saveourtool.save.backend.repository.LnkContestProjectRepository +import com.saveourtool.save.domain.ProjectCoordinates import com.saveourtool.save.entities.* import com.saveourtool.save.utils.debug import com.saveourtool.save.utils.getLogger @@ -98,6 +99,15 @@ class LnkContestProjectService( } } + /** + * @param projectCoordinates + * @param contestName + * @return whether project by [projectCoordinates] is enrolled into a contest by [contestName] + */ + fun isEnrolled(projectCoordinates: ProjectCoordinates, contestName: String): Boolean = lnkContestProjectRepository.findByContestNameAndProjectOrganizationNameAndProjectName( + contestName, projectCoordinates.organizationName, projectCoordinates.projectName + ) != null + companion object { @Suppress("GENERIC_VARIABLE_WRONG_DECLARATION") private val logger = getLogger() diff --git a/save-backend/src/test/kotlin/com/saveourtool/save/backend/controllers/RunExecutionControllerTest.kt b/save-backend/src/test/kotlin/com/saveourtool/save/backend/controllers/RunExecutionControllerTest.kt index 880f011afb..5d3abcc968 100644 --- a/save-backend/src/test/kotlin/com/saveourtool/save/backend/controllers/RunExecutionControllerTest.kt +++ b/save-backend/src/test/kotlin/com/saveourtool/save/backend/controllers/RunExecutionControllerTest.kt @@ -75,6 +75,7 @@ class RunExecutionControllerTest( sdk = Jdk("8"), execCmd = "execCmd", batchSizeForAnalyzer = "batchSizeForAnalyzer", + testingType = TestingType.PUBLIC_TESTS, ) // /initializeAgents diff --git a/save-cloud-common/src/commonMain/kotlin/com/saveourtool/save/entities/Project.kt b/save-cloud-common/src/commonMain/kotlin/com/saveourtool/save/entities/Project.kt index 92b22fc6cf..4084e8c9b6 100644 --- a/save-cloud-common/src/commonMain/kotlin/com/saveourtool/save/entities/Project.kt +++ b/save-cloud-common/src/commonMain/kotlin/com/saveourtool/save/entities/Project.kt @@ -15,7 +15,7 @@ import kotlinx.serialization.Serializable * @property organization * @property email * @property numberOfContainers - * @property contestRating + * @property contestRating global rating based on all contest results associated with this project */ @Entity @Serializable diff --git a/save-cloud-common/src/commonMain/kotlin/com/saveourtool/save/entities/RunExecutionRequest.kt b/save-cloud-common/src/commonMain/kotlin/com/saveourtool/save/entities/RunExecutionRequest.kt index c6c1829aff..f994fe5e3c 100644 --- a/save-cloud-common/src/commonMain/kotlin/com/saveourtool/save/entities/RunExecutionRequest.kt +++ b/save-cloud-common/src/commonMain/kotlin/com/saveourtool/save/entities/RunExecutionRequest.kt @@ -3,6 +3,7 @@ package com.saveourtool.save.entities import com.saveourtool.save.domain.FileKey import com.saveourtool.save.domain.ProjectCoordinates import com.saveourtool.save.domain.Sdk +import com.saveourtool.save.execution.TestingType import kotlinx.serialization.Serializable /** @@ -12,6 +13,8 @@ import kotlinx.serialization.Serializable * @property sdk * @property execCmd * @property batchSizeForAnalyzer + * @property testingType a [TestingType] for this execution + * @property contestName if [testingType] is [TestingType.CONTEST_MODE], then this property contains name of the associated contest */ @Serializable data class RunExecutionRequest( @@ -23,4 +26,13 @@ data class RunExecutionRequest( val sdk: Sdk, val execCmd: String? = null, val batchSizeForAnalyzer: String? = null, -) + + val testingType: TestingType, + val contestName: String? = null, +) { + init { + require((testingType == TestingType.CONTEST_MODE) xor (contestName == null)) { + "RunExecutionRequest.contestName shouldn't be set unless testingType is ${TestingType.CONTEST_MODE}" + } + } +} diff --git a/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/utils/ReactorUtils.kt b/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/utils/ReactorUtils.kt index cd9b2e6461..ea6bd0505c 100644 --- a/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/utils/ReactorUtils.kt +++ b/save-cloud-common/src/jvmMain/kotlin/com/saveourtool/save/utils/ReactorUtils.kt @@ -50,6 +50,22 @@ fun Mono.lazyDefaultIfEmpty(lazyValue: () -> T): Mono = switchIfEmpty */ fun Flux<*>.thenJust(other: T): Mono = then(Mono.just(other)) +/** + * If content of [this] [Mono] matches [predicate], run [effect]. + * + * @param predicate + * @param effect + * @return always returns [Mono] with the original value. Uses [Mono.flatMap] under the hood, + * so all signals are treated accordingly. + */ +fun Mono.asyncEffectIf(predicate: T.() -> Boolean, effect: (T) -> Mono): Mono = flatMap { value -> + if (predicate(value)) { + effect(value).map { value } + } else { + Mono.just(value) + } +} + /** * Taking from https://projectreactor.io/docs/core/release/reference/#faq.wrap-blocking * diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/ProjectView.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/ProjectView.kt index d88d6e0848..abf834807b 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/ProjectView.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/ProjectView.kt @@ -355,9 +355,11 @@ class ProjectView : AbstractView(f files = state.files.map { it.toStorageKey() }, sdk = selectedSdk, execCmd = state.execCmd.takeUnless { it.isBlank() }, - batchSizeForAnalyzer = state.batchSizeForAnalyzer.takeUnless { it.isBlank() } + batchSizeForAnalyzer = state.batchSizeForAnalyzer.takeUnless { it.isBlank() }, + testingType = testingType, + contestName = testingType.takeIf { it == TestingType.CONTEST_MODE }?.let { state.selectedContest.name } ) - submitRequest("/run/trigger?testingType=$testingType", jsonHeaders, Json.encodeToString(executionRequest)) + submitRequest("/run/trigger", jsonHeaders, Json.encodeToString(executionRequest)) } private fun submitRequest(url: String, headers: Headers, body: dynamic) {