Skip to content

Commit

Permalink
Calculate contest rating for projects and organizations (#1181)
Browse files Browse the repository at this point in the history
### What's done:
* Calculate contest rating for projects and organizations
  • Loading branch information
kgevorkyan authored Sep 8, 2022
1 parent 4a034cb commit 936f595
Show file tree
Hide file tree
Showing 12 changed files with 181 additions and 19 deletions.
4 changes: 4 additions & 0 deletions db/v-2/tables/project.xml
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,8 @@
</addColumn>
</changeSet>

<changeSet id="project-make-contest-rating-double" author="kgevorkyan">
<modifyDataType tableName="project" columnName="contest_rating" newDataType="double DEFAULT 0.0"/>
</changeSet>

</databaseChangeLog>
75 changes: 71 additions & 4 deletions save-backend/backend-api-docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -3598,8 +3598,8 @@
"tags": [
"projects"
],
"summary": "Get all avaliable projects.",
"description": "Get all projects, avaliable for current user.",
"summary": "Get all available projects.",
"description": "Get all projects, available for current user.",
"operationId": "getProjects",
"parameters": [
{
Expand Down Expand Up @@ -4352,6 +4352,73 @@
]
}
},
"/api/v1/organizations/{organizationName}/get-organization-contest-rating": {
"get": {
"tags": [
"organizations"
],
"summary": "Get organization contest rating.",
"description": "Get organization contest rating.",
"operationId": "getOrganizationContestRating",
"parameters": [
{
"name": "organizationName",
"in": "path",
"description": "name of an organization",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "X-Authorization-Source",
"in": "header",
"required": true,
"example": "basic"
}
],
"responses": {
"200": {
"description": "Successfully get an organization contest rating.",
"content": {
"*/*": {
"schema": {
"type": "number",
"format": "double"
}
}
}
},
"404": {
"description": "Could not find an organization with such name.",
"content": {
"*/*": {
"schema": {
"type": "number",
"format": "double"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"*/*": {
"schema": {
"type": "number",
"format": "double"
}
}
}
}
},
"security": [
{
"basic": []
}
]
}
},
"/api/v1/organizations/{organizationName}/avatar": {
"get": {
"tags": [
Expand Down Expand Up @@ -7053,8 +7120,8 @@
"$ref": "#/components/schemas/Organization"
},
"contestRating": {
"type": "integer",
"format": "int64"
"type": "number",
"format": "double"
},
"id": {
"type": "integer",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.saveourtool.save.backend.security.OrganizationPermissionEvaluator
import com.saveourtool.save.backend.service.GitService
import com.saveourtool.save.backend.service.LnkUserOrganizationService
import com.saveourtool.save.backend.service.OrganizationService
import com.saveourtool.save.backend.service.ProjectService
import com.saveourtool.save.backend.service.TestSuitesService
import com.saveourtool.save.backend.service.TestSuitesSourceService
import com.saveourtool.save.backend.storage.TestSuitesSourceSnapshotStorage
Expand Down Expand Up @@ -59,6 +60,7 @@ internal class OrganizationController(
private val testSuitesSourceService: TestSuitesSourceService,
private val testSuitesService: TestSuitesService,
private val testSuitesSourceSnapshotStorage: TestSuitesSourceSnapshotStorage,
private val projectService: ProjectService
) {
@GetMapping("/all")
@PreAuthorize("permitAll()")
Expand Down Expand Up @@ -427,6 +429,41 @@ internal class OrganizationController(
ResponseEntity.ok("Git credentials and corresponding data successfully deleted")
}

/**
* @param organizationName
* @param authentication
* @return contest rating for organization
*/
@GetMapping("/{organizationName}/get-organization-contest-rating")
@RequiresAuthorizationSourceHeader
@PreAuthorize("isAuthenticated()")
@Operation(
method = "Get",
summary = "Get organization contest rating.",
description = "Get organization contest rating.",
)
@Parameters(
Parameter(name = "organizationName", `in` = ParameterIn.PATH, description = "name of an organization", required = true),
)
@ApiResponse(responseCode = "200", description = "Successfully get an organization contest rating.")
@ApiResponse(responseCode = "404", description = "Could not find an organization with such name.")
fun getOrganizationContestRating(
@PathVariable organizationName: String,
authentication: Authentication,
): Mono<Double> = Mono.just(organizationName)
.flatMap {
organizationService.findByName(it).toMono()
}
.switchIfEmptyToNotFound {
"Could not find an organization with name $organizationName."
}
.flatMap {
projectService.getNotDeletedProjectsByOrganizationName(organizationName, authentication).collectList()
}
.map { projectsList ->
projectsList.sumOf { it.contestRating }
}

private fun cleanupStorageData(testSuite: TestSuite) {
testSuitesSourceSnapshotStorage.findKey(
testSuite.source.organization.name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ class ProjectController(
@PreAuthorize("permitAll()")
@Operation(
method = "GET",
summary = "Get all avaliable projects.",
description = "Get all projects, avaliable for current user.",
summary = "Get all available projects.",
description = "Get all projects, available for current user.",
)
@ApiResponse(responseCode = "200", description = "Projects successfully fetched.")
fun getProjects(
Expand Down Expand Up @@ -159,13 +159,7 @@ class ProjectController(
fun getNonDeletedProjectsByOrganizationName(
@RequestParam organizationName: String,
authentication: Authentication?,
): Flux<Project> = projectService.findByOrganizationName(organizationName)
.filter {
it.status != ProjectStatus.DELETED
}
.filter {
projectPermissionEvaluator.hasPermission(authentication, it, Permission.READ)
}
): Flux<Project> = projectService.getNotDeletedProjectsByOrganizationName(organizationName, authentication)

@PostMapping("/save")
@RequiresAuthorizationSourceHeader
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import org.springframework.transaction.annotation.Transactional
class LnkContestProjectService(
private val lnkContestProjectRepository: LnkContestProjectRepository,
private val lnkContestExecutionService: LnkContestExecutionService,
private val projectService: ProjectService,
) {
/**
* @param contestName name of a [Contest]
Expand Down Expand Up @@ -96,9 +97,21 @@ class LnkContestProjectService(
lnkContestProject.bestExecution = newExecution
lnkContestProject.bestScore = newExecution.score
lnkContestProjectRepository.save(lnkContestProject)

updateProjectContestRating(project)
}
}

private fun updateProjectContestRating(project: Project) {
val projectContestRating = lnkContestProjectRepository.findByProject(project).mapNotNull {
it.bestScore
}.sum()

projectService.updateProject(project.apply {
this.contestRating = projectContestRating
})
}

/**
* @param projectCoordinates
* @param contestName
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,22 @@ class ProjectService(
return projects
}

/**
* @param organizationName
* @param authentication
* @return list of not deleted projects
*/
fun getNotDeletedProjectsByOrganizationName(
organizationName: String,
authentication: Authentication?,
): Flux<Project> = findByOrganizationName(organizationName)
.filter {
it.status != ProjectStatus.DELETED
}
.filter {
projectPermissionEvaluator.hasPermission(authentication, it, Permission.READ)
}

/**
* @param projectFilters
* @return project's with filter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import com.saveourtool.save.utils.ScoreType
import com.saveourtool.save.utils.calculateScore
import com.saveourtool.save.utils.debug
import com.saveourtool.save.utils.getLogger
import com.saveourtool.save.utils.isValidScore

import org.apache.commons.io.FilenameUtils
import org.slf4j.Logger
Expand Down Expand Up @@ -246,7 +247,12 @@ class TestExecutionService(private val testExecutionRepository: TestExecutionRep
expectedChecks += counters.expectedChecks
unexpectedChecks += counters.unexpectedChecks

score = toDto().calculateScore(scoreType = ScoreType.F_MEASURE)
val executionScore = toDto().calculateScore(scoreType = ScoreType.F_MEASURE)

if (!executionScore.isValidScore()) {
log.error("Execution score for execution id $id is invalid: $executionScore")
}
score = executionScore
}
executionRepository.save(execution)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,17 @@ import org.junit.jupiter.api.extension.ExtendWith
import org.mockito.kotlin.*
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.mock.mockito.MockBean
import org.springframework.boot.test.mock.mockito.MockBeans
import org.springframework.context.annotation.Import
import org.springframework.test.context.junit.jupiter.SpringExtension
import java.util.Optional
import kotlin.math.abs

@ExtendWith(SpringExtension::class)
@Import(LnkContestProjectService::class)
@MockBeans(
MockBean(ProjectService::class),
)
@Suppress("UnsafeCallOnNullableType")
class LnkContestProjectServiceTest {
@Autowired private lateinit var lnkContestProjectService: LnkContestProjectService
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ data class Project(
columnDefinition = "",
)
var organization: Organization,
var contestRating: Long = 0,
var contestRating: Double = 0.0,
) {
/**
* id of project
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,23 @@ fun ExecutionDto.calculateScore(scoreType: ScoreType): Double = when (type) {
else -> 0.0
}

/**
* @return true if value is in range (0, 100); false otherwise
*/
fun Double.isValidScore() = this.toInt().isValidScore()

/**
* @return true if value is in range (0, 100); false otherwise
*/
fun Int.isValidScore() = this in 0..100

private fun ExecutionDto.calculateScoreForContestMode(scoreType: ScoreType): Double = when (scoreType) {
ScoreType.F_MEASURE -> calculateFmeasure()
else -> TODO("Invalid score type for contest mode!")
}

private fun ExecutionDto.calculateFmeasure(): Double {
val denominator = getPrecisionRate() + getRecallRate()
val denominator = (getPrecisionRate() + getRecallRate())
return if (denominator == 0) {
0.0
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import com.saveourtool.save.frontend.externals.fontawesome.fontAwesomeIcon
import com.saveourtool.save.utils.calculateRate
import com.saveourtool.save.utils.getPrecisionRate
import com.saveourtool.save.utils.getRecallRate
import com.saveourtool.save.utils.isValidScore

import csstype.ClassName
import csstype.Width
Expand Down Expand Up @@ -100,12 +101,22 @@ internal class ExecutionStatisticsValues(executionDto: ExecutionDto?) {
?: "0"
this.precisionRate = executionDto
?.let {
"${it.getPrecisionRate()}"
val precisionRate = it.getPrecisionRate()
if (precisionRate.isValidScore()) {
precisionRate.toString()
} else {
"N/A"
}
}
?: "0"
this.recallRate = executionDto
?.let {
"${it.getRecallRate()}"
val recallRate = it.getRecallRate()
if (recallRate.isValidScore()) {
recallRate.toString()
} else {
"N/A"
}
}
?: "0"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -613,7 +613,7 @@ class OrganizationView : AbstractView<OrganizationProps, OrganizationViewState>(
topProject?.let {
scoreCard {
name = it.name
contestScore = it.contestRating.toDouble()
contestScore = it.contestRating
url = "#/${props.organizationName}/${it.name}"
}
}
Expand Down

0 comments on commit 936f595

Please sign in to comment.