From 471e44c5b229e5b52047eaf81554d9ee9da9cd00 Mon Sep 17 00:00:00 2001 From: Julien Lancelot Date: Wed, 28 Jun 2017 14:31:19 +0200 Subject: [PATCH] SONAR-9482 Show ws returns profile related info only --- .../server/qualityprofile/ws/ShowAction.java | 92 ++++++++---- .../qualityprofile/ws/show-example.json | 4 +- .../qualityprofile/ws/ShowActionTest.java | 133 +++++++++++++++++- .../main/protobuf/ws-qualityprofiles.proto | 20 ++- 4 files changed, 204 insertions(+), 45 deletions(-) diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/ShowAction.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/ShowAction.java index 7b4020baf94..eb15750a717 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/ShowAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/ShowAction.java @@ -19,20 +19,43 @@ */ package org.sonar.server.qualityprofile.ws; +import java.util.Map; +import org.sonar.api.resources.Language; +import org.sonar.api.resources.Languages; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; import org.sonar.api.server.ws.WebService.NewAction; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.organization.OrganizationDto; +import org.sonar.db.qualityprofile.ActiveRuleCountQuery; +import org.sonar.db.qualityprofile.QProfileDto; import org.sonarqube.ws.QualityProfiles.ShowWsResponse; -import org.sonarqube.ws.QualityProfiles.ShowWsResponse.CompareToSonarWay; +import org.sonarqube.ws.QualityProfiles.ShowWsResponse.QualityProfile; +import static java.util.Collections.singletonList; +import static org.sonar.api.rule.RuleStatus.DEPRECATED; +import static org.sonar.api.utils.DateUtils.formatDateTime; +import static org.sonar.core.util.Protobuf.setNullable; import static org.sonar.core.util.Uuids.UUID_EXAMPLE_01; +import static org.sonar.server.ws.WsUtils.checkFound; import static org.sonar.server.ws.WsUtils.writeProtobuf; import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters.PARAM_COMPARE_TO_SONAR_WAY; import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters.PARAM_PROFILE; public class ShowAction implements QProfileWsAction { + private final DbClient dbClient; + private final QProfileWsSupport qProfileWsSupport; + private final Languages languages; + + public ShowAction(DbClient dbClient, QProfileWsSupport qProfileWsSupport, Languages languages) { + this.dbClient = dbClient; + this.qProfileWsSupport = qProfileWsSupport; + this.languages = languages; + } + @Override public void define(WebService.NewController controller) { NewAction show = controller.createAction("show") @@ -55,29 +78,50 @@ public class ShowAction implements QProfileWsAction { @Override public void handle(Request request, Response response) throws Exception { - ShowWsResponse showWsResponse = ShowWsResponse.newBuilder() - .setProfile(ShowWsResponse.QualityProfile.newBuilder() - .setKey("AU-TpxcA-iU5OvuD2FL3") - .setName("My Company Profile") - .setLanguage("cs") - .setLanguageName("C#") - .setIsInherited(true) - .setIsBuiltIn(false) - .setIsDefault(false) - .setParentKey("AU-TpxcA-iU5OvuD2FL1") - .setParentName("Parent Company Profile") - .setActiveRuleCount(10) - .setActiveDeprecatedRuleCount(0) - .setProjectCount(7) - .setRuleUpdatedAt("2016-12-22T19:10:03+0100") - .setLastUsed("2016-12-01T19:10:03+0100")) - .setCompareToSonarWay(CompareToSonarWay.newBuilder() - .setProfile("iU5OvuD2FLz") - .setProfileName("Sonar way") - .setMissingRuleCount(4) - .build()) - .build(); - writeProtobuf(showWsResponse, request, response); + try (DbSession dbSession = dbClient.openSession(false)) { + QProfileDto profile = qProfileWsSupport.getProfile(dbSession, QProfileReference.fromKey(request.mandatoryParam(PARAM_PROFILE))); + OrganizationDto organization = qProfileWsSupport.getOrganization(dbSession, profile); + boolean isDefault = dbClient.defaultQProfileDao().isDefault(dbSession, profile.getOrganizationUuid(), profile.getKee()); + ActiveRuleCountQuery.Builder builder = ActiveRuleCountQuery.builder().setOrganization(organization); + long activeRuleCount = countActiveRulesByQuery(dbSession, profile, builder); + long deprecatedActiveRuleCount = countActiveRulesByQuery(dbSession, profile, builder.setRuleStatus(DEPRECATED)); + long projectCount = countProjectsByOrganizationAndProfiles(dbSession, organization, profile); + writeProtobuf(buildResponse(profile, isDefault, getLanguage(profile), activeRuleCount, deprecatedActiveRuleCount, projectCount), request, response); + } + } + + private long countActiveRulesByQuery(DbSession dbSession, QProfileDto profile, ActiveRuleCountQuery.Builder queryBuilder) { + Map result = dbClient.activeRuleDao().countActiveRulesByQuery(dbSession, queryBuilder.setProfiles(singletonList(profile)).build()); + return result.getOrDefault(profile.getKee(), 0L); + } + + private long countProjectsByOrganizationAndProfiles(DbSession dbSession, OrganizationDto organization, QProfileDto profile) { + Map projects = dbClient.qualityProfileDao().countProjectsByOrganizationAndProfiles(dbSession, organization, singletonList(profile)); + return projects.getOrDefault(profile.getKee(), 0L); + } + + public Language getLanguage(QProfileDto profile) { + Language language = languages.get(profile.getLanguage()); + checkFound(language, "Quality Profile with key '%s' does not exist", profile.getKee()); + return language; + } + + private static ShowWsResponse buildResponse(QProfileDto profile, boolean isDefault, Language language, long activeRules, long deprecatedActiveRules, long projects) { + QualityProfile.Builder profileBuilder = QualityProfile.newBuilder() + .setKey(profile.getKee()) + .setName(profile.getName()) + .setLanguage(profile.getLanguage()) + .setLanguageName(language.getName()) + .setIsBuiltIn(profile.isBuiltIn()) + .setIsDefault(isDefault) + .setIsInherited(profile.getParentKee() != null) + .setActiveRuleCount(activeRules) + .setActiveDeprecatedRuleCount(deprecatedActiveRules) + .setProjectCount(projects); + setNullable(profile.getRulesUpdatedAt(), profileBuilder::setRulesUpdatedAt); + setNullable(profile.getLastUsed(), last -> profileBuilder.setLastUsed(formatDateTime(last))); + setNullable(profile.getUserUpdatedAt(), userUpdatedAt -> profileBuilder.setUserUpdatedAt(formatDateTime(userUpdatedAt))); + return ShowWsResponse.newBuilder().setProfile(profileBuilder).build(); } } diff --git a/server/sonar-server/src/main/resources/org/sonar/server/qualityprofile/ws/show-example.json b/server/sonar-server/src/main/resources/org/sonar/server/qualityprofile/ws/show-example.json index 60f03aaf168..a628efa37c2 100644 --- a/server/sonar-server/src/main/resources/org/sonar/server/qualityprofile/ws/show-example.json +++ b/server/sonar-server/src/main/resources/org/sonar/server/qualityprofile/ws/show-example.json @@ -7,12 +7,10 @@ "isInherited": true, "isBuiltIn": false, "isDefault": false, - "parentKey": "AU-TpxcA-iU5OvuD2FL1", - "parentName": "Parent Company Profile", "activeRuleCount": 10, "activeDeprecatedRuleCount": 0, "projectCount": 7, - "ruleUpdatedAt": "2016-12-22T19:10:03+0100", + "rulesUpdatedAt": "2016-12-22T19:10:03+0100", "lastUsed": "2016-12-01T19:10:03+0100" }, "compareToSonarWay": { diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/ShowActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/ShowActionTest.java index b7f9b98f35d..5b90834477b 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/ShowActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/ShowActionTest.java @@ -22,23 +22,45 @@ package org.sonar.server.qualityprofile.ws; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.sonar.api.resources.Language; +import org.sonar.api.resources.Languages; import org.sonar.api.server.ws.WebService; +import org.sonar.api.utils.DateUtils; import org.sonar.db.DbTester; import org.sonar.db.qualityprofile.QProfileDto; +import org.sonar.server.exceptions.NotFoundException; +import org.sonar.server.organization.TestDefaultOrganizationProvider; +import org.sonar.server.tester.UserSessionRule; +import org.sonar.server.ws.TestRequest; import org.sonar.server.ws.WsActionTester; +import org.sonarqube.ws.MediaTypes; +import org.sonarqube.ws.QualityProfiles.ShowWsResponse; import static java.util.stream.IntStream.range; import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.api.rule.RuleStatus.DEPRECATED; +import static org.sonar.api.utils.DateUtils.parseDateTime; import static org.sonar.server.language.LanguageTesting.newLanguage; import static org.sonar.test.JsonAssert.assertJson; +import static org.sonarqube.ws.QualityProfiles.ShowWsResponse.QualityProfile; +import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters.PARAM_PROFILE; public class ShowActionTest { + private static Language XOO1 = newLanguage("xoo1"); + private static Language XOO2 = newLanguage("xoo2"); + private static Languages LANGUAGES = new Languages(XOO1, XOO2); + @Rule public DbTester db = DbTester.create(); + @Rule + public UserSessionRule userSession = UserSessionRule.standalone(); + @Rule + public ExpectedException expectedException = ExpectedException.none(); - private WsActionTester ws = new WsActionTester(new ShowAction()); + private WsActionTester ws = new WsActionTester( + new ShowAction(db.getDbClient(), new QProfileWsSupport(db.getDbClient(), userSession, TestDefaultOrganizationProvider.from(db)), LANGUAGES)); @Test public void test_definition() { @@ -49,13 +71,11 @@ public class ShowActionTest { assertThat(action.since()).isEqualTo("6.5"); WebService.Param profile = action.param("profile"); - assertThat(profile).isNotNull(); assertThat(profile.isRequired()).isTrue(); assertThat(profile.isInternal()).isFalse(); assertThat(profile.description()).isNotEmpty(); WebService.Param compareToSonarWay = action.param("compareToSonarWay"); - assertThat(compareToSonarWay).isNotNull(); assertThat(compareToSonarWay.isRequired()).isFalse(); assertThat(compareToSonarWay.isInternal()).isTrue(); assertThat(compareToSonarWay.description()).isNotEmpty(); @@ -63,9 +83,102 @@ public class ShowActionTest { } @Test - public void test_example() { + public void profile_info() { + QProfileDto profile = db.qualityProfiles().insert(db.getDefaultOrganization(), p -> p.setLanguage(XOO1.getKey())); + + ShowWsResponse result = call(ws.newRequest().setParam(PARAM_PROFILE, profile.getKee())); + + assertThat(result.getProfile()) + .extracting(QualityProfile::getKey, QualityProfile::getName, QualityProfile::getIsBuiltIn, QualityProfile::getLanguage, QualityProfile::getLanguageName, + QualityProfile::getIsInherited) + .containsExactly(profile.getKee(), profile.getName(), profile.isBuiltIn(), profile.getLanguage(), XOO1.getName(), false); + } + + @Test + public void default_profile() { + QProfileDto profile = db.qualityProfiles().insert(db.getDefaultOrganization(), p -> p.setLanguage(XOO1.getKey())); + db.qualityProfiles().setAsDefault(profile); + + ShowWsResponse result = call(ws.newRequest().setParam(PARAM_PROFILE, profile.getKee())); + + assertThat(result.getProfile().getIsDefault()).isTrue(); + } + + @Test + public void non_default_profile() { + QProfileDto profile = db.qualityProfiles().insert(db.getDefaultOrganization(), p -> p.setLanguage(XOO1.getKey())); + QProfileDto defaultProfile = db.qualityProfiles().insert(db.getDefaultOrganization(), p -> p.setLanguage(XOO1.getKey())); + db.qualityProfiles().setAsDefault(defaultProfile); + + ShowWsResponse result = call(ws.newRequest().setParam(PARAM_PROFILE, profile.getKee())); + + assertThat(result.getProfile().getIsDefault()).isFalse(); + } + + @Test + public void map_dates() { + long time = DateUtils.parseDateTime("2016-12-22T19:10:03+0100").getTime(); + QProfileDto profile = db.qualityProfiles().insert(db.getDefaultOrganization(), p -> p + .setLanguage(XOO1.getKey()) + .setRulesUpdatedAt("2016-12-21T19:10:03+0100") + .setLastUsed(time) + .setUserUpdatedAt(time)); + + ShowWsResponse result = call(ws.newRequest().setParam(PARAM_PROFILE, profile.getKee())); + + assertThat(result.getProfile().getRulesUpdatedAt()).isEqualTo("2016-12-21T19:10:03+0100"); + assertThat(parseDateTime(result.getProfile().getLastUsed()).getTime()).isEqualTo(time); + assertThat(parseDateTime(result.getProfile().getUserUpdatedAt()).getTime()).isEqualTo(time); + } + + @Test + public void statistics() { + QProfileDto profile = db.qualityProfiles().insert(db.getDefaultOrganization(), p -> p.setLanguage(XOO1.getKey())); + // Active rules + range(0, 10) + .mapToObj(i -> db.rules().insertRule(r -> r.setLanguage(XOO1.getKey())).getDefinition()) + .forEach(r -> db.qualityProfiles().activateRule(profile, r)); + // Deprecated rules + range(0, 3) + .mapToObj(i -> db.rules().insertRule(r -> r.setLanguage(XOO1.getKey()).setStatus(DEPRECATED)).getDefinition()) + .forEach(r -> db.qualityProfiles().activateRule(profile, r)); + // Projects + range(0, 7) + .mapToObj(i -> db.components().insertPrivateProject()) + .forEach(project -> db.qualityProfiles().associateWithProject(project, profile)); + + ShowWsResponse result = call(ws.newRequest().setParam(PARAM_PROFILE, profile.getKee())); + + assertThat(result.getProfile()) + .extracting(QualityProfile::getActiveRuleCount, QualityProfile::getActiveDeprecatedRuleCount, QualityProfile::getProjectCount) + .containsExactly(13L, 3L, 7L); + } + + @Test + public void fail_if_profile_language_is_not_supported() { + QProfileDto profile = db.qualityProfiles().insert(db.getDefaultOrganization(), p -> p.setKee("unknown-profile").setLanguage("kotlin")); + + expectedException.expect(NotFoundException.class); + expectedException.expectMessage("Quality Profile with key 'unknown-profile' does not exist"); + + call(ws.newRequest().setParam(PARAM_PROFILE, profile.getKee())); + } + + @Test + public void fail_if_profile_does_not_exist() { + expectedException.expect(NotFoundException.class); + expectedException.expectMessage("Quality Profile with key 'unknown-profile' does not exist"); + + call(ws.newRequest().setParam(PARAM_PROFILE, "unknown-profile")); + } + + @Test + public void json_example() { Language cs = newLanguage("cs", "C#"); - QProfileDto parentProfile = db.qualityProfiles().insert(db.getDefaultOrganization(), p -> p.setKee("AU-TpxcA-iU5OvuD2FL1").setName("Parent Company Profile")); + QProfileDto parentProfile = db.qualityProfiles().insert(db.getDefaultOrganization(), + p -> p.setKee("AU-TpxcA-iU5OvuD2FL1") + .setName("Parent Company Profile") + .setLanguage(cs.getKey())); QProfileDto profile = db.qualityProfiles().insert(db.getDefaultOrganization(), p -> p .setKee("AU-TpxcA-iU5OvuD2FL3") .setName("My Company Profile") @@ -82,8 +195,14 @@ public class ShowActionTest { .mapToObj(i -> db.components().insertPrivateProject()) .forEach(project -> db.qualityProfiles().associateWithProject(project, profile)); - String result = ws.newRequest().execute().getInput(); + ws = new WsActionTester(new ShowAction(db.getDbClient(), new QProfileWsSupport(db.getDbClient(), userSession, TestDefaultOrganizationProvider.from(db)), new Languages(cs))); + String result = ws.newRequest().setParam(PARAM_PROFILE, profile.getKee()).execute().getInput(); + + assertJson(result).ignoreFields("rulesUpdatedAt", "lastUsed", "userUpdatedAt", "compareToSonarWay").isSimilarTo(ws.getDef().responseExampleAsString()); + } - assertJson(result).isSimilarTo(ws.getDef().responseExampleAsString()); + private ShowWsResponse call(TestRequest request) { + TestRequest wsRequest = request.setMediaType(MediaTypes.PROTOBUF); + return wsRequest.executeProtobuf(ShowWsResponse.class); } } diff --git a/sonar-ws/src/main/protobuf/ws-qualityprofiles.proto b/sonar-ws/src/main/protobuf/ws-qualityprofiles.proto index 0dab9b3c06f..0f1ddd6cb0b 100644 --- a/sonar-ws/src/main/protobuf/ws-qualityprofiles.proto +++ b/sonar-ws/src/main/protobuf/ws-qualityprofiles.proto @@ -111,17 +111,15 @@ message ShowWsResponse { optional string language = 3; optional string languageName = 4; optional bool isInherited = 5; - optional string parentKey = 6; - optional string parentName = 7; - optional bool isDefault = 8; - optional int64 activeRuleCount = 9; - optional int64 activeDeprecatedRuleCount = 12; - optional int64 projectCount = 10; - optional string ruleUpdatedAt = 11; - optional string lastUsed = 13; - optional string userUpdatedAt = 14; - optional string organization = 15; - optional bool isBuiltIn = 16; + optional bool isDefault = 6; + optional int64 activeRuleCount = 7; + optional int64 activeDeprecatedRuleCount = 8; + optional int64 projectCount = 9; + optional string rulesUpdatedAt = 10; + optional string lastUsed = 11; + optional string userUpdatedAt = 12; + optional string organization = 13; + optional bool isBuiltIn = 14; } message CompareToSonarWay { -- 2.39.5