diff --git a/save-backend/backend-api-docs.json b/save-backend/backend-api-docs.json index 45564570f9..d85a340a40 100644 --- a/save-backend/backend-api-docs.json +++ b/save-backend/backend-api-docs.json @@ -1758,13 +1758,13 @@ }, "responses": { "200": { - "description": "Successfully fetched non-deleted projects.", + "description": "Successfully fetched non-deleted organizations.", "content": { "*/*": { "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/Organization" + "$ref": "#/components/schemas/OrganizationDto" } } } @@ -1777,7 +1777,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/Organization" + "$ref": "#/components/schemas/OrganizationDto" } } } @@ -7166,6 +7166,10 @@ "VIEWER" ] } + }, + "globalRating": { + "type": "number", + "format": "double" } } }, diff --git a/save-backend/src/main/kotlin/com/saveourtool/save/backend/controllers/OrganizationController.kt b/save-backend/src/main/kotlin/com/saveourtool/save/backend/controllers/OrganizationController.kt index 9a3b0ccebe..12d08bdc99 100644 --- a/save-backend/src/main/kotlin/com/saveourtool/save/backend/controllers/OrganizationController.kt +++ b/save-backend/src/main/kotlin/com/saveourtool/save/backend/controllers/OrganizationController.kt @@ -84,9 +84,21 @@ internal class OrganizationController( summary = "Get non-deleted organizations.", description = "Get non-deleted organizations.", ) - @ApiResponse(responseCode = "200", description = "Successfully fetched non-deleted projects.") - fun getNotDeletedOrganizations(@RequestBody(required = false) organizationFilters: OrganizationFilters?) = - organizationService.getNotDeletedOrganizations(organizationFilters).toFlux() + @ApiResponse(responseCode = "200", description = "Successfully fetched non-deleted organizations.") + fun getNotDeletedOrganizations( + @RequestBody(required = false) organizationFilters: OrganizationFilters?, + authentication: Authentication, + ): Flux = + organizationService.getNotDeletedOrganizations(organizationFilters) + .toFlux() + .flatMap { organization -> + organizationService.getGlobalRating(organization.name, authentication).map { + organization to it + } + } + .map { (organization, rating) -> + organization.toDto().copy(globalRating = rating) + } @GetMapping("/{organizationName}") @PreAuthorize("permitAll()") @@ -458,10 +470,7 @@ internal class OrganizationController( "Could not find an organization with name $organizationName." } .flatMap { - projectService.getNotDeletedProjectsByOrganizationName(organizationName, authentication).collectList() - } - .map { projectsList -> - projectsList.sumOf { it.contestRating } + organizationService.getGlobalRating(organizationName, authentication) } private fun cleanupStorageData(testSuite: TestSuite) { diff --git a/save-backend/src/main/kotlin/com/saveourtool/save/backend/service/OrganizationService.kt b/save-backend/src/main/kotlin/com/saveourtool/save/backend/service/OrganizationService.kt index ae717ebb59..042c2f06f7 100644 --- a/save-backend/src/main/kotlin/com/saveourtool/save/backend/service/OrganizationService.kt +++ b/save-backend/src/main/kotlin/com/saveourtool/save/backend/service/OrganizationService.kt @@ -5,6 +5,7 @@ import com.saveourtool.save.domain.OrganizationSaveStatus import com.saveourtool.save.entities.Organization import com.saveourtool.save.entities.OrganizationStatus import com.saveourtool.save.filters.OrganizationFilters +import org.springframework.security.core.Authentication import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @@ -15,6 +16,7 @@ import org.springframework.transaction.annotation.Transactional */ @Service class OrganizationService( + private val projectService: ProjectService, private val organizationRepository: OrganizationRepository, ) { /** @@ -115,4 +117,16 @@ class OrganizationService( * @return all organizations that were registered in SAVE */ fun findAll(): List = organizationRepository.findAll() + + /** + * @param organizationName + * @param authentication + * @return global rating of organization by name [organizationName] based on ratings of all projects under this organization + */ + fun getGlobalRating(organizationName: String, authentication: Authentication) = + projectService.getNotDeletedProjectsByOrganizationName(organizationName, authentication) + .collectList() + .map { projectsList -> + projectsList.sumOf { it.contestRating } + } } diff --git a/save-cloud-common/src/commonMain/kotlin/com/saveourtool/save/entities/OrganizationDto.kt b/save-cloud-common/src/commonMain/kotlin/com/saveourtool/save/entities/OrganizationDto.kt index 7f9ed5f903..0f330eaf39 100644 --- a/save-cloud-common/src/commonMain/kotlin/com/saveourtool/save/entities/OrganizationDto.kt +++ b/save-cloud-common/src/commonMain/kotlin/com/saveourtool/save/entities/OrganizationDto.kt @@ -15,6 +15,7 @@ import kotlinx.serialization.Serializable * @property description * @property canCreateContests * @property userRoles map where keys are usernames and values are their [Role]s + * @property globalRating */ @Serializable data class OrganizationDto( @@ -25,6 +26,7 @@ data class OrganizationDto( val description: String = "", val canCreateContests: Boolean = false, val userRoles: Map = emptyMap(), + val globalRating: Double? = null, ) : Validatable { /** * Validation of organization name @@ -47,12 +49,13 @@ data class OrganizationDto( * Value that represents an empty [OrganizationDto] */ val empty = OrganizationDto( - "", - null, - null, - "", - false, - emptyMap(), + name = "", + dateCreated = null, + avatar = null, + description = "", + canCreateContests = false, + userRoles = emptyMap(), + globalRating = null, ) } } diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/ScoreCard.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/ScoreCard.kt index 49b82e955f..2d9cec27fe 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/ScoreCard.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/ScoreCard.kt @@ -2,6 +2,7 @@ package com.saveourtool.save.frontend.components.basic +import com.saveourtool.save.frontend.utils.toFixed import csstype.* import react.FC import react.Props @@ -80,7 +81,7 @@ private fun scoreCard() = FC { props -> alignItems = AlignItems.center alignSelf = AlignSelf.start } - +"${props.contestScore}" + +"${props.contestScore.toFixed(2)}" } } div { diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/contests/ContestGlobalRatingView.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/contests/ContestGlobalRatingView.kt index 7ee20e9092..781341d044 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/contests/ContestGlobalRatingView.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/contests/ContestGlobalRatingView.kt @@ -5,7 +5,7 @@ package com.saveourtool.save.frontend.components.views.contests import com.saveourtool.save.domain.Role -import com.saveourtool.save.entities.Organization +import com.saveourtool.save.entities.OrganizationDto import com.saveourtool.save.entities.Project import com.saveourtool.save.filters.OrganizationFilters import com.saveourtool.save.filters.ProjectFilters @@ -63,7 +63,7 @@ external interface ContestGlobalRatingViewState : State, HasSelectedMenu + var organizations: Array /** * All projects @@ -91,7 +91,7 @@ class ContestGlobalRatingView : AbstractView { + columns = columns { column(id = "index", header = "Position") { Fragment.create { td { @@ -121,10 +121,10 @@ class ContestGlobalRatingView : AbstractView Fragment.create { td { - +"4560" + +"${cellProps.value.globalRating?.toFixed(2)}" } } } @@ -185,10 +185,10 @@ class ContestGlobalRatingView : AbstractView Fragment.create { td { - +"1370" + +"${cellProps.value.contestRating.toFixed(2)}" } } } @@ -236,7 +236,7 @@ class ContestGlobalRatingView : AbstractView = post( + val organizationsFromBackend: List = post( url = "$apiUrl/organizations/not-deleted", headers = jsonHeaders, body = Json.encodeToString(filterValue), diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/contests/UserRating.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/contests/UserRating.kt index 294ee05f5b..8a33dd8543 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/contests/UserRating.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/contests/UserRating.kt @@ -6,7 +6,7 @@ package com.saveourtool.save.frontend.components.views.contests -import com.saveourtool.save.entities.Organization +import com.saveourtool.save.entities.OrganizationDto import com.saveourtool.save.entities.Project import com.saveourtool.save.frontend.TabMenuBar import com.saveourtool.save.frontend.externals.fontawesome.faArrowRight @@ -39,7 +39,7 @@ enum class UserRatingTab { companion object : TabMenuBar { // The string is the postfix of a [regexForUrlClassification] for parsing the url - private val postfixInRegex = values().map { it.name.lowercase() }.joinToString("|") + private val postfixInRegex = values().joinToString("|") { it.name.lowercase() } override val nameOfTheHeadUrlSection = "" override val defaultTab: UserRatingTab = UserRatingTab.ORGS override val regexForUrlClassification = Regex("/${FrontendRoutes.CONTESTS_GLOBAL_RATING.path}/($postfixInRegex)") @@ -76,18 +76,17 @@ private fun ChildrenBuilder.renderingProjectChampionsTable(projects: Set) { +private fun ChildrenBuilder.renderingOrganizationChampionsTable(organizations: Set) { organizations.forEachIndexed { i, organization -> div { className = ClassName("row text-muted pb-3 mb-3 border-bottom border-gray mx-2") @@ -115,11 +114,10 @@ private fun ChildrenBuilder.renderingOrganizationChampionsTable(organizations: S } } - // FixMe: add rating after kirill's changes div { className = ClassName("col-lg-4") p { - +"4560" + +"${organization.globalRating?.toFixed(2)}" } } } @@ -133,9 +131,9 @@ private fun ChildrenBuilder.renderingOrganizationChampionsTable(organizations: S private fun userRating() = VFC { val (selectedTab, setSelectedTab) = useState(UserRatingTab.ORGS) - val (organizations, setOrganizations) = useState>(emptySet()) + val (organizations, setOrganizations) = useState>(emptySet()) useRequest { - val organizationsFromBackend: List = post( + val organizationsFromBackend: List = post( url = "$apiUrl/organizations/not-deleted", headers = jsonHeaders, body = undefined, diff --git a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/utils/Utils.kt b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/utils/Utils.kt index 8aa43e73d9..4af63879f9 100644 --- a/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/utils/Utils.kt +++ b/save-frontend/src/main/kotlin/com/saveourtool/save/frontend/utils/Utils.kt @@ -108,3 +108,8 @@ internal fun ChildrenBuilder.multilineTextWithIndices(text: String) { * @return true if string is invalid */ internal fun String?.isInvalid(maxLength: Int) = this.isNullOrBlank() || this.contains(" ") || this.length > maxLength + +/** + * @param digits number of digits to round to + */ +internal fun Double.toFixed(digits: Int) = asDynamic().toFixed(digits)