diff --git a/backend/src/main/kotlin/dev/dres/api/rest/types/evaluation/ApiEvaluationState.kt b/backend/src/main/kotlin/dev/dres/api/rest/types/evaluation/ApiEvaluationState.kt index bda41113c..bb92bb023 100644 --- a/backend/src/main/kotlin/dev/dres/api/rest/types/evaluation/ApiEvaluationState.kt +++ b/backend/src/main/kotlin/dev/dres/api/rest/types/evaluation/ApiEvaluationState.kt @@ -18,7 +18,7 @@ data class ApiEvaluationState( val taskId: String?, val taskStatus: ApiTaskStatus, val taskTemplateId: String?, - val timeLeft: Long, + val timeLeft: Long?, val timeElapsed: Long ) { constructor(run: InteractiveRunManager, context: RunActionContext) : this( @@ -27,7 +27,7 @@ data class ApiEvaluationState( run.currentTask(context)?.taskId, run.currentTask(context)?.status ?: ApiTaskStatus.NO_TASK, run.currentTaskTemplate(context).templateId, - run.timeLeft(context) / 1000, + run.timeLeft(context)?.div(1000), run.timeElapsed(context) / 1000 ) } diff --git a/backend/src/main/kotlin/dev/dres/api/rest/types/template/tasks/options/ApiScoreOption.kt b/backend/src/main/kotlin/dev/dres/api/rest/types/template/tasks/options/ApiScoreOption.kt index 09403f938..66940ab86 100644 --- a/backend/src/main/kotlin/dev/dres/api/rest/types/template/tasks/options/ApiScoreOption.kt +++ b/backend/src/main/kotlin/dev/dres/api/rest/types/template/tasks/options/ApiScoreOption.kt @@ -10,7 +10,7 @@ import dev.dres.data.model.template.task.options.DbScoreOption * @version 1.1.0 */ enum class ApiScoreOption { - KIS, AVS, LEGACY_AVS; + KIS, AVS, LEGACY_AVS, NOOP; /** * Converts this [ApiScoreOption] to a [DbScoreOption] representation. Requires an ongoing transaction. @@ -21,5 +21,6 @@ enum class ApiScoreOption { KIS -> DbScoreOption.KIS AVS -> DbScoreOption.AVS LEGACY_AVS -> DbScoreOption.LEGACY_AVS + NOOP -> DbScoreOption.NOOP } } diff --git a/backend/src/main/kotlin/dev/dres/mgmt/TemplateManager.kt b/backend/src/main/kotlin/dev/dres/mgmt/TemplateManager.kt index 896edcd20..5156a94d7 100644 --- a/backend/src/main/kotlin/dev/dres/mgmt/TemplateManager.kt +++ b/backend/src/main/kotlin/dev/dres/mgmt/TemplateManager.kt @@ -93,9 +93,15 @@ object TemplateManager { /* Update task type information. */ val taskTypes = apiEvaluationTemplate.taskTypes.map { it.name }.toTypedArray() - dbEvaluationTemplate.taskTypes.removeAll( - DbTaskType.query(DbTaskType::evaluation eq dbEvaluationTemplate and not(DbTaskType::name.containsIn(*taskTypes))) - ) + val taskTypesToDeleteQuery = DbTaskType.query(DbTaskType::evaluation eq dbEvaluationTemplate and not(DbTaskType::name.containsIn(*taskTypes))) + /* Manual cleanup, as removeAll does not cleanup related children. */ + val configuredOptionsToDelIds= taskTypesToDeleteQuery.toList().map { + it.configurations.toList().map{opt -> opt.entityId} + }.flatten().toTypedArray() + DbConfiguredOption.all().toList().filter{configuredOptionsToDelIds.contains(it.entityId)}.forEach{it.delete()} + + dbEvaluationTemplate.taskTypes.removeAll(taskTypesToDeleteQuery) + for (apiTaskType in apiEvaluationTemplate.taskTypes) { val taskType = DbTaskType.findOrNew(DbTaskType.query((DbTaskType::name eq apiTaskType.name) and (DbTaskType::evaluation eq dbEvaluationTemplate))) { diff --git a/backend/src/main/kotlin/dev/dres/run/InteractiveAsynchronousRunManager.kt b/backend/src/main/kotlin/dev/dres/run/InteractiveAsynchronousRunManager.kt index a658417b9..ce3422c29 100644 --- a/backend/src/main/kotlin/dev/dres/run/InteractiveAsynchronousRunManager.kt +++ b/backend/src/main/kotlin/dev/dres/run/InteractiveAsynchronousRunManager.kt @@ -371,22 +371,25 @@ class InteractiveAsynchronousRunManager( /** * Returns the time in milliseconds that is left until the end of the currently running task for the given team. * Only works if the [InteractiveAsynchronousRunManager] is in state [RunManagerStatus.ACTIVE]. - * If no task is running or is running perpetually, this method returns -1L. + * If no task is running, this method returns -1L. + * If the task runs perpetually, this method returns NULL. * * @param context The [RunActionContext] used for the invocation. - * @return Time remaining until the task will end or -1, if no task is running. + * @return Time remaining until the task will end or -1, if no task is running. NULL if the task runs perpetually. */ - override fun timeLeft(context: RunActionContext): Long = this.stateLock.read { - + override fun timeLeft(context: RunActionContext): Long? = this.stateLock.read { val currentTaskRun = this.currentTask(context) - - return if (currentTaskRun?.isRunning == true && currentTaskRun.duration != null) { // TODO what is the semantic of a perpetual IA task? - max( - 0L, - currentTaskRun.duration!! * 1000L - (System.currentTimeMillis() - currentTaskRun.started!!) + InteractiveRunManager.COUNTDOWN_DURATION - ) + return if (currentTaskRun?.duration == null) { + null } else { - -1L + if (currentTaskRun.isRunning) { + max( + 0L, + currentTaskRun.duration!! * 1000L - (System.currentTimeMillis() - currentTaskRun.started!!) + InteractiveRunManager.COUNTDOWN_DURATION + ) + } else { + -1L + } } } diff --git a/backend/src/main/kotlin/dev/dres/run/InteractiveRunManager.kt b/backend/src/main/kotlin/dev/dres/run/InteractiveRunManager.kt index 44bd9ff5a..28d5872ba 100644 --- a/backend/src/main/kotlin/dev/dres/run/InteractiveRunManager.kt +++ b/backend/src/main/kotlin/dev/dres/run/InteractiveRunManager.kt @@ -104,9 +104,9 @@ interface InteractiveRunManager : RunManager { * this method returns -1L. * * @param context The [RunActionContext] used for the invocation. - * @return Time remaining until the task will end or -1, if no task is running. + * @return Time remaining until the task will end or -1, if no task is running. Null if the task runs perpetually. */ - fun timeLeft(context: RunActionContext): Long + fun timeLeft(context: RunActionContext): Long? /** * Returns the time in milliseconds that has elapsed since the start of the currently running task. @@ -178,4 +178,4 @@ interface InteractiveRunManager : RunManager { * @return true on success, false otherwise */ fun overrideReadyState(context: RunActionContext, viewerId: String): Boolean -} \ No newline at end of file +} diff --git a/backend/src/main/kotlin/dev/dres/run/InteractiveSynchronousRunManager.kt b/backend/src/main/kotlin/dev/dres/run/InteractiveSynchronousRunManager.kt index 052bb5de7..87fe9eaca 100644 --- a/backend/src/main/kotlin/dev/dres/run/InteractiveSynchronousRunManager.kt +++ b/backend/src/main/kotlin/dev/dres/run/InteractiveSynchronousRunManager.kt @@ -116,7 +116,10 @@ class InteractiveSynchronousRunManager( /* End ongoing tasks upon initialization (in case server crashed during task execution). */ for (task in this.evaluation.taskRuns) { if (task.isRunning || task.status == ApiTaskStatus.RUNNING) { - task.end() + /* We exclude perpetual tasks here */ + if(task.duration != null){ + task.end() + } } } @@ -367,12 +370,15 @@ class InteractiveSynchronousRunManager( /** * Returns the time in milliseconds that is left until the end of the current [DbTask]. * Only works if the [RunManager] is in right [RunManagerStatus]. If no task is running, - * OR a perpetual task is running, this method returns -1L. + * this method returns -1L. If the task is to run perpetually, NULL is returned. * - * @return Time remaining until the task will end or -1, if no task is running. + * @return Time remaining until the task will end or -1, if no task is running. NULL if the task runs perpetually. */ - override fun timeLeft(context: RunActionContext): Long { - return if (this.evaluation.currentTaskRun?.status == ApiTaskStatus.RUNNING && this.evaluation.currentTaskRun?.duration != null) { + override fun timeLeft(context: RunActionContext): Long? { + return if(this.evaluation.currentTaskRun?.duration == null){ + null + }else{ + if (this.evaluation.currentTaskRun?.status == ApiTaskStatus.RUNNING) { val currentTaskRun = this.currentTask(context) ?: throw IllegalStateException("SynchronizedRunManager is in status ${this.status} but has no active TaskRun. This is a serious error!") max( @@ -381,7 +387,7 @@ class InteractiveSynchronousRunManager( ) } else { -1L - } + }} } /** diff --git a/backend/src/main/kotlin/dev/dres/run/updatables/EndOnSubmitUpdatable.kt b/backend/src/main/kotlin/dev/dres/run/updatables/EndOnSubmitUpdatable.kt index 7d0fb8864..80b418494 100644 --- a/backend/src/main/kotlin/dev/dres/run/updatables/EndOnSubmitUpdatable.kt +++ b/backend/src/main/kotlin/dev/dres/run/updatables/EndOnSubmitUpdatable.kt @@ -46,18 +46,20 @@ class EndOnSubmitUpdatable(private val manager: InteractiveRunManager) : Updatab val limit = taskType.configuration["LIMIT_CORRECT_PER_TEAM"]?.toIntOrNull() ?: 1 /* Count number of correct submissions per team. */ - if (this.manager.timeLeft(context) > 0) { - for (s in submissions) { - for (a in s.answerSets.toList()) { - if (a.status == DbVerdictStatus.CORRECT) { - teams[s.team.id] = teams[s.team.id]!! + 1 + if (currentTask.duration != null) { + if (this.manager.timeLeft(context)!! > 0) { + for (s in submissions) { + for (a in s.answerSets.toList()) { + if (a.status == DbVerdictStatus.CORRECT) { + teams[s.team.id] = teams[s.team.id]!! + 1 + } } } - } - /* If all teams have reached the limit, end the task. */ - if (teams.all { it.value >= limit }) { - this.manager.abortTask(context) + /* If all teams have reached the limit, end the task. */ + if (teams.all { it.value >= limit }) { + this.manager.abortTask(context) + } } } } @@ -74,4 +76,4 @@ class EndOnSubmitUpdatable(private val manager: InteractiveRunManager) : Updatab */ override fun shouldBeUpdated(runStatus: RunManagerStatus, taskStatus: ApiTaskStatus?): Boolean = (runStatus == RunManagerStatus.ACTIVE && taskStatus == ApiTaskStatus.RUNNING) -} \ No newline at end of file +} diff --git a/backend/src/main/kotlin/dev/dres/run/updatables/ProlongOnSubmitUpdatable.kt b/backend/src/main/kotlin/dev/dres/run/updatables/ProlongOnSubmitUpdatable.kt index 8ce41edcb..59205e830 100644 --- a/backend/src/main/kotlin/dev/dres/run/updatables/ProlongOnSubmitUpdatable.kt +++ b/backend/src/main/kotlin/dev/dres/run/updatables/ProlongOnSubmitUpdatable.kt @@ -34,6 +34,10 @@ class ProlongOnSubmitUpdatable(private val manager: InteractiveRunManager): Upda override fun update(runStatus: RunManagerStatus, taskStatus: ApiTaskStatus?, context: RunActionContext) { if (runStatus == RunManagerStatus.ACTIVE && taskStatus == ApiTaskStatus.RUNNING) { val currentTask = this.manager.currentTask(context) ?: return + /* This is only sensible to do, if the task has a duration */ + if(currentTask.duration == null){ + return + } val taskType = this.manager.template.taskTypes.firstOrNull { it.name == currentTask.template.taskType }!! val prolongOnSubmit = taskType.taskOptions.contains(ApiTaskOption.PROLONG_ON_SUBMISSION) if (prolongOnSubmit) { @@ -47,7 +51,7 @@ class ProlongOnSubmitUpdatable(private val manager: InteractiveRunManager): Upda if (lastSubmission == null || (correctOnly && lastSubmission.answerSets.asSequence().all { it.status != DbVerdictStatus.CORRECT })) { return } - val timeLeft = Math.floorDiv(this.manager.timeLeft(context), 1000) + val timeLeft = Math.floorDiv(this.manager.timeLeft(context)!!, 1000) if (timeLeft in 0 until limit) { this.manager.adjustDuration(context, prolongBy) } @@ -64,4 +68,4 @@ class ProlongOnSubmitUpdatable(private val manager: InteractiveRunManager): Upda */ override fun shouldBeUpdated(runStatus: RunManagerStatus, taskStatus: ApiTaskStatus?): Boolean = (runStatus == RunManagerStatus.ACTIVE && taskStatus == ApiTaskStatus.RUNNING) -} \ No newline at end of file +} diff --git a/doc/oas-client.json b/doc/oas-client.json index f62c56225..a3541647c 100644 --- a/doc/oas-client.json +++ b/doc/oas-client.json @@ -2,8 +2,8 @@ "openapi" : "3.0.3", "info" : { "title" : "DRES Client API", - "version" : "2.0.4", - "description" : "Client API for DRES (Distributed Retrieval Evaluation Server), Version 2.0.4" + "version" : "2.0.5-SNAPSHOT", + "description" : "Client API for DRES (Distributed Retrieval Evaluation Server), Version 2.0.5-SNAPSHOT" }, "paths" : { "/api/v2/client/evaluation/currentTask/{evaluationId}" : { @@ -1159,7 +1159,7 @@ "format" : "int64" } }, - "required" : [ "name", "taskGroup", "taskType", "duration" ] + "required" : [ "name", "taskGroup", "taskType" ] }, "ApiEvaluation" : { "type" : "object", @@ -1282,7 +1282,7 @@ "format" : "int64" } }, - "required" : [ "evaluationId", "evaluationStatus", "taskStatus", "timeLeft", "timeElapsed" ] + "required" : [ "evaluationId", "evaluationStatus", "taskStatus", "timeElapsed" ] }, "ApiEvaluationStatus" : { "type" : "string", @@ -1383,7 +1383,7 @@ "format" : "int64" } }, - "required" : [ "id", "name", "type", "group", "duration", "taskId", "status" ] + "required" : [ "id", "name", "type", "group", "taskId", "status" ] }, "ApiTaskStatus" : { "type" : "string", @@ -1413,7 +1413,7 @@ "format" : "int64" } }, - "required" : [ "templateId", "name", "taskGroup", "taskType", "duration" ] + "required" : [ "templateId", "name", "taskGroup", "taskType" ] }, "ApiTeamInfo" : { "type" : "object", @@ -2154,7 +2154,7 @@ "type" : "string" } }, - "required" : [ "name", "taskGroup", "taskType", "duration", "collectionId", "targets", "hints" ] + "required" : [ "name", "taskGroup", "taskType", "collectionId", "targets", "hints" ] }, "ApiTaskType" : { "type" : "object", @@ -2198,7 +2198,7 @@ } } }, - "required" : [ "name", "duration", "targetOption", "hintOptions", "submissionOptions", "taskOptions", "scoreOption", "configuration" ] + "required" : [ "name", "targetOption", "hintOptions", "submissionOptions", "taskOptions", "scoreOption", "configuration" ] }, "ApiHintOption" : { "type" : "string", @@ -2206,7 +2206,7 @@ }, "ApiScoreOption" : { "type" : "string", - "enum" : [ "KIS", "AVS", "LEGACY_AVS" ] + "enum" : [ "KIS", "AVS", "LEGACY_AVS", "NOOP" ] }, "ApiSubmissionOption" : { "type" : "string", diff --git a/doc/oas.json b/doc/oas.json index f35b8b4aa..c089ca293 100644 --- a/doc/oas.json +++ b/doc/oas.json @@ -2,8 +2,8 @@ "openapi" : "3.0.3", "info" : { "title" : "DRES API", - "version" : "2.0.4", - "description" : "API for DRES (Distributed Retrieval Evaluation Server), Version 2.0.4", + "version" : "2.0.5-SNAPSHOT", + "description" : "API for DRES (Distributed Retrieval Evaluation Server), Version 2.0.5-SNAPSHOT", "contact" : { "name" : "The DRES Dev Team", "url" : "https://dres.dev" @@ -5573,7 +5573,7 @@ "format" : "int64" } }, - "required" : [ "name", "taskGroup", "taskType", "duration" ] + "required" : [ "name", "taskGroup", "taskType" ] }, "ApiEvaluation" : { "type" : "object", @@ -5696,7 +5696,7 @@ "format" : "int64" } }, - "required" : [ "evaluationId", "evaluationStatus", "taskStatus", "timeLeft", "timeElapsed" ] + "required" : [ "evaluationId", "evaluationStatus", "taskStatus", "timeElapsed" ] }, "ApiEvaluationStatus" : { "type" : "string", @@ -5797,7 +5797,7 @@ "format" : "int64" } }, - "required" : [ "id", "name", "type", "group", "duration", "taskId", "status" ] + "required" : [ "id", "name", "type", "group", "taskId", "status" ] }, "ApiTaskStatus" : { "type" : "string", @@ -5827,7 +5827,7 @@ "format" : "int64" } }, - "required" : [ "templateId", "name", "taskGroup", "taskType", "duration" ] + "required" : [ "templateId", "name", "taskGroup", "taskType" ] }, "ApiTeamInfo" : { "type" : "object", @@ -6568,7 +6568,7 @@ "type" : "string" } }, - "required" : [ "name", "taskGroup", "taskType", "duration", "collectionId", "targets", "hints" ] + "required" : [ "name", "taskGroup", "taskType", "collectionId", "targets", "hints" ] }, "ApiTaskType" : { "type" : "object", @@ -6612,7 +6612,7 @@ } } }, - "required" : [ "name", "duration", "targetOption", "hintOptions", "submissionOptions", "taskOptions", "scoreOption", "configuration" ] + "required" : [ "name", "targetOption", "hintOptions", "submissionOptions", "taskOptions", "scoreOption", "configuration" ] }, "ApiHintOption" : { "type" : "string", @@ -6620,7 +6620,7 @@ }, "ApiScoreOption" : { "type" : "string", - "enum" : [ "KIS", "AVS", "LEGACY_AVS" ] + "enum" : [ "KIS", "AVS", "LEGACY_AVS", "NOOP" ] }, "ApiSubmissionOption" : { "type" : "string", diff --git a/frontend/src/app/competition/competition-builder/competition-builder-task-dialog/advanced-builder-dialog/advanced-builder-dialog.component.ts b/frontend/src/app/competition/competition-builder/competition-builder-task-dialog/advanced-builder-dialog/advanced-builder-dialog.component.ts index 3beb6f4dc..130aec526 100644 --- a/frontend/src/app/competition/competition-builder/competition-builder-task-dialog/advanced-builder-dialog/advanced-builder-dialog.component.ts +++ b/frontend/src/app/competition/competition-builder/competition-builder-task-dialog/advanced-builder-dialog/advanced-builder-dialog.component.ts @@ -1,11 +1,11 @@ import { Component, Inject, OnInit } from '@angular/core'; -import { CompetitionFormBuilder } from '../competition-form.builder'; +import { TaskTemplateFormBuilder } from '../../../../template/template-builder/task-template-form.builder'; import { AppConfig } from '../../../../app.config'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { VideoPlayerSegmentBuilderData } from '../video-player-segment-builder/video-player-segment-builder.component'; export class AdvancedBuilderDialogData { - builder: CompetitionFormBuilder; + builder: TaskTemplateFormBuilder; } // TODO rewrite diff --git a/frontend/src/app/competition/competition-builder/competition-builder-task-dialog/competition-builder-task-dialog.component.ts b/frontend/src/app/competition/competition-builder/competition-builder-task-dialog/competition-builder-task-dialog.component.ts index 77aabbc22..7770aab94 100644 --- a/frontend/src/app/competition/competition-builder/competition-builder-task-dialog/competition-builder-task-dialog.component.ts +++ b/frontend/src/app/competition/competition-builder/competition-builder-task-dialog/competition-builder-task-dialog.component.ts @@ -4,7 +4,7 @@ import { UntypedFormControl, UntypedFormGroup } from '@angular/forms'; import { Observable } from 'rxjs'; import { filter, first } from 'rxjs/operators'; import { AppConfig } from '../../../app.config'; -import { CompetitionFormBuilder } from './competition-form.builder'; +import { TaskTemplateFormBuilder } from '../../../template/template-builder/task-template-form.builder'; import { VideoPlayerSegmentBuilderData } from './video-player-segment-builder/video-player-segment-builder.component'; import { AdvancedBuilderDialogComponent, @@ -47,8 +47,8 @@ export class CompetitionBuilderTaskDialogComponent { units = ['FRAME_NUMBER', 'SECONDS', 'MILLISECONDS', 'TIMECODE']; /** Data source for list of {@link MediaCollection}. Loaded upon construction of the dialog. */ mediaCollectionSource: Observable; - /** The {@link CompetitionFormBuilder} used by this dialogue. */ - builder: CompetitionFormBuilder; + /** The {@link TaskTemplateFormBuilder} used by this dialogue. */ + builder: TaskTemplateFormBuilder; @ViewChild('videoPlayer', { static: false }) video: ElementRef; viewLayout = 'list'; showVideo = false; @@ -63,7 +63,7 @@ export class CompetitionBuilderTaskDialogComponent { public config: AppConfig, private builderService: TemplateBuilderService // To make the compiler happy ) { - this.builder = new CompetitionFormBuilder(this.data.taskGroup, this.data.taskType, this.collectionService, this.builderService, this.data.task); + this.builder = new TaskTemplateFormBuilder(this.data.taskGroup, this.data.taskType, this.collectionService, this.builderService, this.data.task); this.form = this.builder.form; this.mediaCollectionSource = this.collectionService.getApiV2CollectionList(); } @@ -76,7 +76,7 @@ export class CompetitionBuilderTaskDialogComponent { uploaded = (taskData: string) => { const task = JSON.parse(taskData) as ApiTaskTemplate; - this.builder = new CompetitionFormBuilder(this.data.taskGroup, this.data.taskType, this.collectionService, this.builderService, task); + this.builder = new TaskTemplateFormBuilder(this.data.taskGroup, this.data.taskType, this.collectionService, this.builderService, task); this.form = this.builder.form; console.log('Loaded task: ' + JSON.stringify(task)); }; diff --git a/frontend/src/app/competition/competition-builder/competition-builder.component.ts b/frontend/src/app/competition/competition-builder/competition-builder.component.ts index a6a864ce4..6f3ec52de 100644 --- a/frontend/src/app/competition/competition-builder/competition-builder.component.ts +++ b/frontend/src/app/competition/competition-builder/competition-builder.component.ts @@ -10,7 +10,7 @@ import { CompetitionBuilderTaskGroupDialogData, } from './competition-builder-task-group-dialog/competition-builder-task-group.component'; import { MatTable } from '@angular/material/table'; -import { CompetitionBuilderTaskTypeDialogComponent } from './competition-builder-task-type-dialog/competition-builder-task-type-dialog.component'; +import { CreateTaskTypeDialogComponent } from '../../template/template-builder/components/create-task-type-dialog/create-task-type-dialog.component'; import { CompetitionBuilderTaskDialogComponent, CompetitionBuilderTaskDialogData, @@ -257,7 +257,7 @@ export class CompetitionBuilderComponent implements OnInit, OnDestroy, Deactivat } public addTaskType(type?: ApiTaskType) { - const dialogRef = this.dialog.open(CompetitionBuilderTaskTypeDialogComponent, { data: type ? type : null, width: '750px' }); + const dialogRef = this.dialog.open(CreateTaskTypeDialogComponent, { data: type ? type : null, width: '750px' }); dialogRef .afterClosed() .pipe(filter((g) => g != null)) diff --git a/frontend/src/app/competition/competition-builder/competition-builder.module.ts b/frontend/src/app/competition/competition-builder/competition-builder.module.ts index 416c6b318..4f69a8b6f 100644 --- a/frontend/src/app/competition/competition-builder/competition-builder.module.ts +++ b/frontend/src/app/competition/competition-builder/competition-builder.module.ts @@ -17,7 +17,7 @@ import { MatAutocompleteModule } from '@angular/material/autocomplete'; import { MatSelectModule } from '@angular/material/select'; import { CompetitionBuilderTaskGroupDialogComponent } from './competition-builder-task-group-dialog/competition-builder-task-group.component'; import { MatChipsModule } from '@angular/material/chips'; -import { CompetitionBuilderTaskTypeDialogComponent } from './competition-builder-task-type-dialog/competition-builder-task-type-dialog.component'; +import { CreateTaskTypeDialogComponent } from '../../template/template-builder/components/create-task-type-dialog/create-task-type-dialog.component'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatCardModule } from '@angular/material/card'; import { VideoPlayerSegmentBuilderComponent } from './competition-builder-task-dialog/video-player-segment-builder/video-player-segment-builder.component'; @@ -66,7 +66,6 @@ import { ColorPickerModule } from 'ngx-color-picker'; CompetitionBuilderComponent, CompetitionBuilderTaskDialogComponent, CompetitionBuilderTaskGroupDialogComponent, - CompetitionBuilderTaskTypeDialogComponent, VideoPlayerSegmentBuilderComponent, AdvancedBuilderDialogComponent, VideoPlayerSegmentBuilderDialogComponent, diff --git a/frontend/src/app/evaluation/task-controls/task-controls.component.html b/frontend/src/app/evaluation/task-controls/task-controls.component.html index 1c407ddc0..e1a980499 100644 --- a/frontend/src/app/evaluation/task-controls/task-controls.component.html +++ b/frontend/src/app/evaluation/task-controls/task-controls.component.html @@ -42,7 +42,7 @@ *ngIf="(isAdmin | async) && (runState | async)?.taskStatus === 'RUNNING'" class="mat-body" style="align-self: center;" > - Time left: {{ (runState | async)?.timeLeft | formatTime:false }} + Time left: {{ ((runState | async)?.timeLeft ? ((runState | async)?.timeLeft | formatTime:false) : '∞')}}