diff options
author | Anita Stanisz <106669481+anita-stanisz-sonarsource@users.noreply.github.com> | 2024-11-26 16:41:55 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2024-11-29 20:03:07 +0000 |
commit | 41f89aeb5b6d67f6c9329771ee097766943f130f (patch) | |
tree | d4247c6206a22b56e6b886ee208dddceb3bc2c6a | |
parent | 559fa67b8ecaf77462beca6486d275fafbfe9633 (diff) | |
download | sonarqube-41f89aeb5b6d67f6c9329771ee097766943f130f.tar.gz sonarqube-41f89aeb5b6d67f6c9329771ee097766943f130f.zip |
SONAR-23619 Add aiCodeAssurance field to search quality gate endpoint (#12362)
8 files changed, 88 insertions, 54 deletions
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/ProjectQgateAssociationDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/ProjectQgateAssociationDto.java index b2ef04f7f28..b17164e9211 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/ProjectQgateAssociationDto.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/qualitygate/ProjectQgateAssociationDto.java @@ -32,6 +32,7 @@ public class ProjectQgateAssociationDto { private String name; private String gateUuid; private boolean containsAiCode; + private boolean aiCodeSupportedByQg; public ProjectQgateAssociationDto() { // do nothing @@ -82,4 +83,12 @@ public class ProjectQgateAssociationDto { this.containsAiCode = containsAiCode; return this; } + + public boolean isAiCodeSupportedByQg() { + return aiCodeSupportedByQg; + } + + public void setAiCodeSupportedByQg(boolean aiCodeSupportedByQg) { + this.aiCodeSupportedByQg = aiCodeSupportedByQg; + } } diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/qualitygate/ProjectQgateAssociationMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/qualitygate/ProjectQgateAssociationMapper.xml index ae3b21a3eda..329eb55f484 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/qualitygate/ProjectQgateAssociationMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/qualitygate/ProjectQgateAssociationMapper.xml @@ -4,7 +4,7 @@ <mapper namespace="org.sonar.db.qualitygate.ProjectQgateAssociationMapper"> <select id="selectProjects" parameterType="map" resultType="ProjectQgateAssociation"> - SELECT proj.uuid as uuid, proj.kee as "key", proj.name as name, qg.uuid as gateUuid, proj.contains_ai_code as containsAiCode + SELECT proj.uuid as uuid, proj.kee as "key", proj.name as name, qg.uuid as gateUuid, proj.contains_ai_code as containsAiCode, qg.ai_code_supported as aiCodeSupportedByQg FROM projects proj LEFT JOIN project_qgates prqg ON prqg.project_uuid=proj.uuid AND prqg.quality_gate_uuid = #{query.gateUuid, jdbcType=VARCHAR} LEFT JOIN quality_gates qg ON qg.uuid = prqg.quality_gate_uuid diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/ai/code/assurance/AiCodeAssuranceVerifierTest.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/ai/code/assurance/AiCodeAssuranceVerifierTest.java index 3479bb80164..0714c002662 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/ai/code/assurance/AiCodeAssuranceVerifierTest.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/ai/code/assurance/AiCodeAssuranceVerifierTest.java @@ -45,17 +45,6 @@ class AiCodeAssuranceVerifierTest { private AiCodeAssuranceVerifier underTest; @ParameterizedTest - @MethodSource("provideParams") - void isAiCodeAssured(Edition edition, boolean containsAiCode, boolean expected) { - when(platformEditionProvider.get()).thenReturn(Optional.of(edition)); - underTest = new AiCodeAssuranceVerifier(platformEditionProvider, dbClient); - - when(projectDto.getContainsAiCode()).thenReturn(containsAiCode); - - assertThat(underTest.isAiCodeAssured(projectDto.getContainsAiCode())).isEqualTo(expected); - } - - @ParameterizedTest @MethodSource("isAiCodeAssuredForProject") void isAiCodeAssuredForProject(boolean containsAiCode, boolean aiCodeSupportedQg, boolean expected) { when(platformEditionProvider.get()).thenReturn(Optional.of(Edition.DEVELOPER)); @@ -75,6 +64,7 @@ class AiCodeAssuranceVerifierTest { mockProjectAndQualityGate(containsAiCode, aiCodeSupportedQg); assertThat(underTest.getAiCodeAssurance(projectDto)).isEqualTo(expected); + assertThat(underTest.getAiCodeAssurance(containsAiCode, aiCodeSupportedQg)).isEqualTo(expected); } @ParameterizedTest diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/ws/SearchActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/ws/SearchActionIT.java index efe0c0a9cff..ab7887753b7 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/ws/SearchActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/ws/SearchActionIT.java @@ -19,8 +19,13 @@ */ package org.sonar.server.qualitygate.ws; -import org.junit.Rule; -import org.junit.Test; +import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.sonar.api.server.ws.WebService; import org.sonar.db.DbClient; import org.sonar.db.DbTester; @@ -28,6 +33,7 @@ import org.sonar.db.component.ComponentDto; import org.sonar.db.project.ProjectDto; import org.sonar.db.qualitygate.QualityGateDto; import org.sonar.db.user.UserDto; +import org.sonar.server.ai.code.assurance.AiCodeAssurance; import org.sonar.server.ai.code.assurance.AiCodeAssuranceVerifier; import org.sonar.server.component.TestComponentFinder; import org.sonar.server.exceptions.NotFoundException; @@ -40,6 +46,7 @@ import static java.lang.String.valueOf; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.AssertionsForClassTypes.tuple; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.sonar.api.server.ws.WebService.SelectionMode.ALL; @@ -52,13 +59,13 @@ import static org.sonar.server.qualitygate.ws.QualityGatesWsParameters.PARAM_PAG import static org.sonar.server.qualitygate.ws.QualityGatesWsParameters.PARAM_PAGE_SIZE; import static org.sonarqube.ws.client.user.UsersWsParameters.PARAM_SELECTED; -public class SearchActionIT { +class SearchActionIT { - @Rule - public UserSessionRule userSession = UserSessionRule.standalone(); + @RegisterExtension + UserSessionRule userSession = UserSessionRule.standalone(); - @Rule - public DbTester db = DbTester.create(); + @RegisterExtension + DbTester db = DbTester.create(); private final DbClient dbClient = db.getDbClient(); private final AiCodeAssuranceVerifier aiCodeAssuranceVerifier = mock(AiCodeAssuranceVerifier.class); @@ -66,8 +73,13 @@ public class SearchActionIT { new QualityGatesWsSupport(dbClient, userSession, TestComponentFinder.from(db)), aiCodeAssuranceVerifier); private final WsActionTester ws = new WsActionTester(underTest); + @BeforeEach + void setUp() { + when(aiCodeAssuranceVerifier.getAiCodeAssurance(anyBoolean(), anyBoolean())).thenReturn(AiCodeAssurance.NONE); + } + @Test - public void search_projects_of_a_quality_gate() { + void search_projects_of_a_quality_gate() { ComponentDto project = db.components().insertPublicProject().getMainBranchComponent(); QualityGateDto qualityGate = db.qualityGates().insertQualityGate(); db.qualityGates().associateProjectToQualityGate(db.components().getProjectDtoByMainBranch(project), qualityGate); @@ -82,7 +94,7 @@ public class SearchActionIT { } @Test - public void return_empty_association() { + void return_empty_association() { QualityGateDto qualityGate = db.qualityGates().insertQualityGate(); SearchResponse response = ws.newRequest() @@ -93,7 +105,7 @@ public class SearchActionIT { } @Test - public void return_all_projects() { + void return_all_projects() { QualityGateDto qualityGate = db.qualityGates().insertQualityGate(); ProjectDto unassociatedProject = db.components().insertPublicProject().getProjectDto(); ProjectDto associatedProject = db.components().insertPublicProject().getProjectDto(); @@ -112,7 +124,7 @@ public class SearchActionIT { } @Test - public void return_only_associated_project() { + void return_only_associated_project() { QualityGateDto qualityGate = db.qualityGates().insertQualityGate(); ProjectDto associatedProject = db.components().insertPublicProject().getProjectDto(); ProjectDto unassociatedProject = db.components().insertPublicProject().getProjectDto(); @@ -130,7 +142,7 @@ public class SearchActionIT { } @Test - public void return_only_unassociated_project() { + void return_only_unassociated_project() { QualityGateDto qualityGate = db.qualityGates().insertQualityGate(); ProjectDto associatedProject = db.components().insertPublicProject().getProjectDto(); ProjectDto unassociatedProject = db.components().insertPublicProject().getProjectDto(); @@ -148,7 +160,7 @@ public class SearchActionIT { } @Test - public void return_only_authorized_projects() { + void return_only_authorized_projects() { QualityGateDto qualityGate = db.qualityGates().insertQualityGate(); ComponentDto project1 = db.components().insertPrivateProject().getMainBranchComponent(); ComponentDto project2 = db.components().insertPrivateProject().getMainBranchComponent(); @@ -169,7 +181,7 @@ public class SearchActionIT { } @Test - public void test_paging() { + void test_paging() { QualityGateDto qualityGate = db.qualityGates().insertQualityGate(); ProjectDto project1 = db.components().insertPublicProject(dto -> dto.setName("proj_1")).getProjectDto(); ProjectDto project2 = db.components().insertPublicProject(dto -> dto.setName("proj_2")).getProjectDto(); @@ -233,7 +245,7 @@ public class SearchActionIT { } @Test - public void test_pagination_on_many_pages() { + void test_pagination_on_many_pages() { QualityGateDto qualityGate = db.qualityGates().insertQualityGate(); for (int i = 0; i < 20; i++) { ProjectDto project = db.components().insertPublicProject().getProjectDto(); @@ -256,7 +268,7 @@ public class SearchActionIT { } @Test - public void test_pagination_on_one_page() { + void test_pagination_on_one_page() { QualityGateDto qualityGate = db.qualityGates().insertQualityGate(); for (int i = 0; i < 20; i++) { ProjectDto project = db.components().insertPublicProject().getProjectDto(); @@ -279,7 +291,7 @@ public class SearchActionIT { } @Test - public void fail_on_unknown_quality_gate() { + void fail_on_unknown_quality_gate() { assertThatThrownBy(() -> ws.newRequest() .setParam(PARAM_GATE_NAME, "unknown") .executeProtobuf(SearchResponse.class)) @@ -287,17 +299,14 @@ public class SearchActionIT { .hasMessageContaining("No quality gate has been found for name unknown"); } - - @Test - public void return_disabled_property_in_projects() { - QualityGateDto qualityGate = db.qualityGates().insertQualityGate(); - ProjectDto project1 = db.components().insertPublicProject(componentDto -> componentDto.setName("proj1"), - projectDto -> projectDto.setContainsAiCode(true)).getProjectDto(); - ProjectDto project2 = db.components().insertPublicProject(componentDto -> componentDto.setName("proj2"), - projectDto -> projectDto.setContainsAiCode(false)).getProjectDto(); - - when(aiCodeAssuranceVerifier.isAiCodeAssured(project1.getContainsAiCode())).thenReturn(true); - when(aiCodeAssuranceVerifier.isAiCodeAssured(project2.getContainsAiCode())).thenReturn(false); + @ParameterizedTest + @MethodSource("aiCodeAssuranceParams") + void return_ai_code_assurance(boolean containsAiCode, boolean aiCodeSupportedByQg, AiCodeAssurance expected) { + QualityGateDto qualityGate = db.qualityGates().insertQualityGate(qg -> qg.setAiCodeSupported(aiCodeSupportedByQg)); + ProjectDto project = db.components().insertPublicProject(componentDto -> componentDto.setName("proj1"), + projectDto -> projectDto.setContainsAiCode(containsAiCode)).getProjectDto(); + db.qualityGates().associateProjectToQualityGate(project, qualityGate); + when(aiCodeAssuranceVerifier.getAiCodeAssurance(project.getContainsAiCode(), qualityGate.isAiCodeSupported())).thenReturn(expected); SearchResponse response = ws.newRequest() .setParam(PARAM_GATE_NAME, valueOf(qualityGate.getName())) @@ -305,14 +314,22 @@ public class SearchActionIT { .executeProtobuf(SearchResponse.class); assertThat(response.getResultsList()) - .extracting(Result::getName, Result::getKey, Result::getIsAiCodeAssured) + .extracting(Result::getName, Result::getKey, result -> result.getAiCodeAssurance().name()) .containsExactlyInAnyOrder( - tuple(project1.getName(), project1.getKey(), true), - tuple(project2.getName(), project2.getKey(), false)); + tuple(project.getName(), project.getKey(), expected.name())); + } + + private static Stream<Arguments> aiCodeAssuranceParams() { + return Stream.of( + Arguments.of(false, false, AiCodeAssurance.NONE), + Arguments.of(false, true, AiCodeAssurance.NONE), + Arguments.of(true, false, AiCodeAssurance.CONTAINS_AI_CODE), + Arguments.of(true, true, AiCodeAssurance.AI_CODE_ASSURED) + ); } @Test - public void definition() { + void definition() { WebService.Action action = ws.getDef(); assertThat(action).isNotNull(); diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/ai/code/assurance/AiCodeAssuranceVerifier.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/ai/code/assurance/AiCodeAssuranceVerifier.java index 7e8a64fb6a0..0854f8ac757 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/ai/code/assurance/AiCodeAssuranceVerifier.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/ai/code/assurance/AiCodeAssuranceVerifier.java @@ -43,10 +43,7 @@ public class AiCodeAssuranceVerifier { } public AiCodeAssurance getAiCodeAssurance(ProjectDto projectDto) { - if (!isSupported) { - return AiCodeAssurance.NONE; - } - if (!projectDto.getContainsAiCode()) { + if (!isSupported || !projectDto.getContainsAiCode()) { return AiCodeAssurance.NONE; } try (DbSession dbSession = dbClient.openSession(false)) { @@ -65,8 +62,13 @@ public class AiCodeAssuranceVerifier { return AiCodeAssurance.AI_CODE_ASSURED.equals(getAiCodeAssurance(projectDto)); } - @Deprecated(forRemoval = true) - public boolean isAiCodeAssured(boolean containsAiCode) { - return isSupported && containsAiCode; + public AiCodeAssurance getAiCodeAssurance(boolean containsAiCode, boolean aiCodeSupportedQg) { + if (!isSupported || !containsAiCode) { + return AiCodeAssurance.NONE; + } + if (aiCodeSupportedQg) { + return AiCodeAssurance.AI_CODE_ASSURED; + } + return AiCodeAssurance.CONTAINS_AI_CODE; } } diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ws/SearchAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ws/SearchAction.java index 4b0ba62e1b7..ec000ac70f5 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ws/SearchAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ws/SearchAction.java @@ -36,6 +36,7 @@ import org.sonar.db.qualitygate.QualityGateDto; import org.sonar.server.ai.code.assurance.AiCodeAssuranceVerifier; import org.sonar.server.user.UserSession; import org.sonarqube.ws.Qualitygates; +import org.sonarqube.ws.Qualitygates.SearchResponse.AiCodeAssurance; import static org.sonar.api.server.ws.WebService.Param.SELECTED; import static org.sonar.api.utils.Paging.forPageIndex; @@ -69,6 +70,8 @@ public class SearchAction implements QualityGatesWsAction { .setSince("4.3") .setResponseExample(Resources.getResource(this.getClass(), "search-example.json")) .setChangelog( + new Change("10.8", "Field 'isAiCodeAssured' response field has been deprecated. Use 'aiCodeAssurance' instead."), + new Change("10.8", "New field 'aiCodeAssurance' in the response."), new Change("10.0", "deprecated 'more' response field has been removed"), new Change("10.0", "Parameter 'gateId' is removed. Use 'gateName' instead."), new Change("8.4", "Parameter 'gateName' added"), @@ -134,11 +137,14 @@ public class SearchAction implements QualityGatesWsAction { .build(); for (ProjectQgateAssociationDto project : paginatedProjects) { + AiCodeAssurance aiCodeAssurance = AiCodeAssurance.valueOf(aiCodeAssuranceVerifier.getAiCodeAssurance(project.getContainsAiCode(), + project.isAiCodeSupportedByQg()).name()); createResponse.addResultsBuilder() .setName(project.getName()) .setKey(project.getKey()) .setSelected(project.getGateUuid() != null) - .setIsAiCodeAssured(aiCodeAssuranceVerifier.isAiCodeAssured(project.getContainsAiCode())); + .setIsAiCodeAssured(AiCodeAssurance.AI_CODE_ASSURED.equals(aiCodeAssurance)) + .setAiCodeAssurance(aiCodeAssurance); } writeProtobuf(createResponse.build(), request, response); diff --git a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/qualitygate/ws/search-example.json b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/qualitygate/ws/search-example.json index 97637865c4e..2ff4dfc36a0 100644 --- a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/qualitygate/ws/search-example.json +++ b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/qualitygate/ws/search-example.json @@ -8,12 +8,14 @@ { "name": "Simple Java project analyzed with the SonarQube Runner", "key": "somple-java", - "selected": true + "selected": true, + "aiCodeAssurance": "CONTAINS_AI_CODE" }, { "name": "My Project", "key": "my-project", - "selected": false + "selected": false, + "aiCodeAssurance": "CONTAINS_AI_CODE" } ] } diff --git a/sonar-ws/src/main/protobuf/ws-qualitygates.proto b/sonar-ws/src/main/protobuf/ws-qualitygates.proto index 3895e02c57d..7d72961142a 100644 --- a/sonar-ws/src/main/protobuf/ws-qualitygates.proto +++ b/sonar-ws/src/main/protobuf/ws-qualitygates.proto @@ -150,6 +150,14 @@ message SearchResponse { optional bool selected = 3; optional string key = 4; optional bool isAiCodeAssured = 5; + optional AiCodeAssurance aiCodeAssurance = 6; + } + + enum AiCodeAssurance { + UNKNOWN_AI_CODE_ASSURANCE = 0; + NONE = 1; + CONTAINS_AI_CODE = 2; + AI_CODE_ASSURED = 3; } } |