From f1a8a72c293d64a5bc33f55879da79f72c1a9839 Mon Sep 17 00:00:00 2001 From: Alexander Frolov Date: Fri, 26 Aug 2022 17:54:20 +0300 Subject: [PATCH] ContestSummaryMenu improvements (#1108) ### What's done: * Slightly improved `ContestSummaryMenu` * Fixed styles for execution labels for non-contest executions. (#1035) --- .../components/basic/ExecutionLabels.kt | 40 +++++--- .../basic/contests/ContestSummaryMenu.kt | 92 +++++++++++++++---- .../components/views/ContestExecutionView.kt | 2 +- .../components/views/ExecutionView.kt | 2 +- 4 files changed, 105 insertions(+), 31 deletions(-) diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/ExecutionLabels.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/ExecutionLabels.kt index 64fd55172f..9a5edf7ede 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/ExecutionLabels.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/ExecutionLabels.kt @@ -129,14 +129,17 @@ internal class ExecutionStatisticsValues(executionDto: ExecutionDto?) { * Function that renders Project version label, execution statistics label, pass rate label and rerun button. * Rerun button is rendered only if [onRerunExecution] is provided. * - * @param executionDto + * @param executionDto execution that should be used as data source + * @param isContest flag that defines whether to use contest styles or not * @param classes [ClassName]s that will be applied to highest div * @param innerClasses [ClassName]s that will be applied to each label * @param height height of label * @param onRerunExecution */ +@Suppress("TOO_MANY_PARAMETERS", "LongParameterList") fun ChildrenBuilder.displayExecutionInfoHeader( executionDto: ExecutionDto?, + isContest: Boolean, classes: String = "", innerClasses: String = "col flex-wrap m-2", height: String = "h-100", @@ -145,29 +148,32 @@ fun ChildrenBuilder.displayExecutionInfoHeader( val relativeWidth = onRerunExecution?.let { "min-vw-25" } ?: "min-vw-33" div { className = ClassName(classes) - displayProjectVersion(executionDto, "$relativeWidth $innerClasses", height) - displayPassRate(executionDto, "$relativeWidth $innerClasses", height) - displayStatistics(executionDto, "$relativeWidth $innerClasses", height) - displayRerunExecutionButton(executionDto, "$relativeWidth $innerClasses", height, onRerunExecution) + displayProjectVersion(executionDto, isContest, "$relativeWidth $innerClasses", height) + displayPassRate(executionDto, isContest, "$relativeWidth $innerClasses", height) + displayStatistics(executionDto, isContest, "$relativeWidth $innerClasses", height) + displayRerunExecutionButton(executionDto, isContest, "$relativeWidth $innerClasses", height, onRerunExecution) } } /** * Function that renders Rerun execution button * - * @param executionDto + * @param executionDto execution that should be used as data source + * @param isContest flag that defines whether to use contest styles or not * @param classes [ClassName]s that will be applied to highest div * @param height height of label * @param onRerunExecution onClick callback */ fun ChildrenBuilder.displayRerunExecutionButton( executionDto: ExecutionDto?, + isContest: Boolean, classes: String = "", height: String = "h-100", onRerunExecution: ((MouseEvent) -> Unit)?, ) { onRerunExecution?.let { val borderColor = when { + !isContest -> "info" executionDto == null -> "secondary" executionDto.status == ExecutionStatus.ERROR || executionDto.failedTests != 0L -> "danger" executionDto.status == ExecutionStatus.RUNNING || executionDto.status == ExecutionStatus.PENDING -> "info" @@ -198,16 +204,19 @@ fun ChildrenBuilder.displayRerunExecutionButton( /** * Function that renders label with project version * - * @param executionDto + * @param executionDto execution that should be used as data source + * @param isContest flag that defines whether to use contest styles or not * @param classes [ClassName]s that will be applied to highest div * @param height height of label */ fun ChildrenBuilder.displayProjectVersion( executionDto: ExecutionDto?, + isContest: Boolean, classes: String = "", height: String = "h-100", ) { val statusColor = when { + !isContest -> "bg-info" executionDto == null -> "bg-secondary" executionDto.status == ExecutionStatus.ERROR || executionDto.failedTests != 0L -> "bg-danger" executionDto.status == ExecutionStatus.RUNNING || executionDto.status == ExecutionStatus.PENDING -> "bg-info" @@ -233,7 +242,7 @@ fun ChildrenBuilder.displayProjectVersion( /** * A function that displays a GIF if tests not found * - * @param executionDto + * @param executionDto execution that should be used as data source */ fun ChildrenBuilder.displayTestNotFound(executionDto: ExecutionDto?) { val count = executionDto?.allTests @@ -266,21 +275,28 @@ fun ChildrenBuilder.displayTestNotFound(executionDto: ExecutionDto?) { /** * Function that renders pass rate label * - * @param executionDto + * @param executionDto execution that should be used as data source + * @param isContest flag that defines whether to use contest styles or not * @param classes [ClassName]s that will be applied to highest div * @param height height of label */ @Suppress("MAGIC_NUMBER", "TOO_LONG_FUNCTION") fun ChildrenBuilder.displayPassRate( executionDto: ExecutionDto?, + isContest: Boolean, classes: String = "", height: String = "h-100", ) { val values = ExecutionStatisticsValues(executionDto) div { className = ClassName(classes) + val styles = if (isContest) { + values.style + } else { + "info" + } div { - className = ClassName("card border-left-${values.style} shadow $height py-2") + className = ClassName("card border-left-$styles shadow $height py-2") div { className = ClassName("card-body") div { @@ -333,13 +349,15 @@ fun ChildrenBuilder.displayPassRate( /** * Function that renders execution statistics label * - * @param executionDto + * @param executionDto execution that should be used as data source + * @param isContest flag that defines whether to use contest styles or not * @param classes [ClassName]s that will be applied to highest div * @param height height of label */ @Suppress("TOO_LONG_FUNCTION", "LongMethod") fun ChildrenBuilder.displayStatistics( executionDto: ExecutionDto?, + isContest: Boolean, classes: String = "", height: String = "h-100", ) { diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/contests/ContestSummaryMenu.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/contests/ContestSummaryMenu.kt index 276fb87c0d..25b2345a37 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/contests/ContestSummaryMenu.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/contests/ContestSummaryMenu.kt @@ -3,7 +3,6 @@ package com.saveourtool.save.frontend.components.basic.contests import com.saveourtool.save.entities.ContestResult -import com.saveourtool.save.frontend.components.basic.scoreCard import com.saveourtool.save.frontend.utils.* import csstype.* @@ -12,6 +11,8 @@ import react.* import react.dom.html.ReactHTML.a import react.dom.html.ReactHTML.div import react.dom.html.ReactHTML.h6 +import react.dom.html.ReactHTML.li +import react.dom.html.ReactHTML.ul import kotlinx.js.jso @@ -30,6 +31,72 @@ external interface ContestSummaryMenuProps : Props { var contestName: String } +private fun ChildrenBuilder.displayTopProjects(sortedResults: List) { + ul { + className = ClassName("col-10 mb-2 list-group") + displayResult( + "Top position", + "Project", + "Organization", + "Score", + null + ) + sortedResults.filter { + it.score != null + } + .forEachIndexed { index, contestResult -> + displayResult( + "${index + 1}. ", + contestResult.projectName, + contestResult.organizationName, + contestResult.score.toString(), + "#/${contestResult.organizationName}/${contestResult.projectName}" + ) + } + } +} + +private fun ChildrenBuilder.displayResult( + topPositionLabel: String, + projectName: String, + organizationName: String, + score: String, + linkToProject: String?, +) { + li { + val disabled = linkToProject?.let { "" } ?: "disabled bg-light" + className = ClassName("list-group-item $disabled") + linkToProject?.let { + a { + href = it + className = ClassName("stretched-link") + } + } + div { + className = ClassName("d-flex justify-content-between") + div { + className = ClassName("row col") + div { + className = ClassName("mr-1 col text-left") + +topPositionLabel + } + div { + className = ClassName("ml-1 col text-center") + +projectName + } + } + div { + className = ClassName("col text-center") + +organizationName + } + div { + className = ClassName("col-1 text-right") + +score + } + } + } +} + /** * @return ReactElement */ @@ -40,9 +107,9 @@ external interface ContestSummaryMenuProps : Props { "AVOID_NULL_CHECKS" ) private fun contestSummaryMenu() = FC { props -> - val (results, setResults) = useState>(emptyList()) + val (sortedResults, setSortedResults) = useState>(emptyList()) useRequest { - val projectResults = get( + val results = get( url = "$apiUrl/contests/${props.contestName}/scores", headers = Headers().also { it.set("Accept", "application/json") @@ -53,7 +120,7 @@ private fun contestSummaryMenu() = FC { props -> it.decodeFromJsonString>() } .sortedByDescending { it.score } - setResults(projectResults) + setSortedResults(results) } div { className = ClassName("mb-3") @@ -63,24 +130,13 @@ private fun contestSummaryMenu() = FC { props -> flexDirection = FlexDirection.column alignItems = AlignItems.center } - if (results.isEmpty()) { + if (sortedResults.isEmpty()) { h6 { className = ClassName("text-center") +"There are no participants yet. You can be the first one to participate in it!" } - } - results.forEach { contestResult -> - div { - className = ClassName("col-10 mb-2") - a { - href = "#/${contestResult.organizationName}/${contestResult.projectName}" - className = ClassName("stretched-link") - } - scoreCard { - name = "${contestResult.organizationName}/${contestResult.projectName}" - contestScore = contestResult.score ?: 0.0 - } - } + } else { + displayTopProjects(sortedResults) } } } diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/ContestExecutionView.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/ContestExecutionView.kt index 81824b0927..58c035c077 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/ContestExecutionView.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/ContestExecutionView.kt @@ -177,7 +177,7 @@ class ContestExecutionView : AbstractView(fals colSpan = tableInstance.columns.size div { className = ClassName("row") - displayExecutionInfoHeader(row.original, "row col-11") + displayExecutionInfoHeader(row.original, true, "row col-11") div { className = ClassName("col-1") pieChart( diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/ExecutionView.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/ExecutionView.kt index a869ec0c69..b044ef2a17 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/ExecutionView.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/ExecutionView.kt @@ -363,7 +363,7 @@ class ExecutionView : AbstractView(false) { override fun ChildrenBuilder.render() { div { div { - displayExecutionInfoHeader(state.executionDto, "row mb-2") { event -> + displayExecutionInfoHeader(state.executionDto, false, "row mb-2") { event -> scope.launch { val response = post( "$apiUrl/run/re-trigger?executionId=${props.executionId}",