From a7ab4d9edfa042c5abe38b8935a8f3265953210a Mon Sep 17 00:00:00 2001 From: Teryk Bellahsene Date: Mon, 20 Jun 2016 18:12:40 +0200 Subject: [PATCH] SONAR-7773 Add number of deprecated rules to WS api/qualityprofiles/search --- .../server/qualityprofile/QProfileLoader.java | 4 -- .../qualityprofile/index/ActiveRuleIndex.java | 9 ++- .../qualityprofile/ws/SearchAction.java | 11 ++-- .../server/qualityprofile/ws/SearchData.java | 10 ++++ .../qualityprofile/ws/SearchDataLoader.java | 13 ++-- ...xample-search.json => search-example.json} | 6 +- .../QProfileServiceMediumTest.java | 60 ++++++++++++++----- .../qualityprofile/ws/SearchActionTest.java | 23 ++++--- .../ws/SearchActionTest/search.json | 7 ++- .../main/protobuf/ws-qualityprofiles.proto | 1 + 10 files changed, 98 insertions(+), 46 deletions(-) rename server/sonar-server/src/main/resources/org/sonar/server/qualityprofile/ws/{example-search.json => search-example.json} (84%) diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileLoader.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileLoader.java index 735a96edaec..4f287322f35 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileLoader.java +++ b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileLoader.java @@ -82,10 +82,6 @@ public class QProfileLoader { } } - public Map countAllActiveRules() { - return activeRuleIndex.countAllByQualityProfileKey(); - } - public Map> getAllProfileStats() { List keys = findAll().stream().map(QualityProfileDto::getKey).collect(Collectors.toList()); return activeRuleIndex.getStatsByProfileKeys(keys); diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/index/ActiveRuleIndex.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/index/ActiveRuleIndex.java index 9a8b2a84372..b3d62dfa3e2 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/index/ActiveRuleIndex.java +++ b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/index/ActiveRuleIndex.java @@ -65,7 +65,14 @@ public class ActiveRuleIndex extends BaseIndex { return countByField(FIELD_ACTIVE_RULE_PROFILE_KEY, QueryBuilders.hasParentQuery(TYPE_RULE, QueryBuilders.boolQuery().mustNot( - QueryBuilders.termQuery(FIELD_RULE_STATUS, "REMOVED")))); + QueryBuilders.termQuery(FIELD_RULE_STATUS, RuleStatus.REMOVED.name())))); + } + + public Map countAllDeprecatedByQualityProfileKey() { + return countByField(FIELD_ACTIVE_RULE_PROFILE_KEY, + QueryBuilders.hasParentQuery(TYPE_RULE, + QueryBuilders.boolQuery().must( + QueryBuilders.termQuery(FIELD_RULE_STATUS, RuleStatus.DEPRECATED.name())))); } private Map countByField(String indexField, QueryBuilder filter) { diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/SearchAction.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/SearchAction.java index 93e2b05fdbf..a77ac9a13af 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/SearchAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/SearchAction.java @@ -60,7 +60,7 @@ public class SearchAction implements QProfileWsAction { .setSince("5.2") .setDescription("List quality profiles.") .setHandler(this) - .setResponseExample(getClass().getResource("example-search.json")); + .setResponseExample(getClass().getResource("search-example.json")); action .createParam(PARAM_LANGUAGE) @@ -95,10 +95,10 @@ public class SearchAction implements QProfileWsAction { private static SearchWsRequest toSearchWsRequest(Request request) { return new SearchWsRequest() - .setProjectKey(request.param(PARAM_PROJECT_KEY)) - .setProfileName(request.param(PARAM_PROFILE_NAME)) - .setDefaults(request.paramAsBoolean(PARAM_DEFAULTS)) - .setLanguage(request.param(PARAM_LANGUAGE)); + .setProjectKey(request.param(PARAM_PROJECT_KEY)) + .setProfileName(request.param(PARAM_PROFILE_NAME)) + .setDefaults(request.paramAsBoolean(PARAM_DEFAULTS)) + .setLanguage(request.param(PARAM_LANGUAGE)); } private SearchWsResponse doHandle(SearchWsRequest request) { @@ -125,6 +125,7 @@ public class SearchAction implements QProfileWsAction { profileBuilder.setRulesUpdatedAt(profile.getRulesUpdatedAt()); } profileBuilder.setActiveRuleCount(data.getActiveRuleCount(profileKey)); + profileBuilder.setActiveDeprecatedRuleCount(data.getActiveDeprecatedRuleCount(profileKey)); if (!profile.isDefault()) { profileBuilder.setProjectCount(data.getProjectCount(profileKey)); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/SearchData.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/SearchData.java index 6910c34e515..d0f5b5d9ff5 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/SearchData.java +++ b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/SearchData.java @@ -30,6 +30,7 @@ import static com.google.common.collect.ImmutableMap.copyOf; public class SearchData { private List profiles; private Map activeRuleCountByProfileKey; + private Map activeDeprecatedRuleCountByProfileKey; private Map projectCountByProfileKey; public List getProfiles() { @@ -46,6 +47,11 @@ public class SearchData { return this; } + public SearchData setActiveDeprecatedRuleCountByProfileKey(Map activeDeprecatedRuleCountByProfileKey) { + this.activeDeprecatedRuleCountByProfileKey = activeDeprecatedRuleCountByProfileKey; + return this; + } + public SearchData setProjectCountByProfileKey(Map projectCountByProfileKey) { this.projectCountByProfileKey = copyOf(projectCountByProfileKey); return this; @@ -58,4 +64,8 @@ public class SearchData { public long getProjectCount(String profileKey) { return firstNonNull(projectCountByProfileKey.get(profileKey), 0L); } + + public long getActiveDeprecatedRuleCount(String profileKey) { + return firstNonNull(activeDeprecatedRuleCountByProfileKey.get(profileKey), 0L); + } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/SearchDataLoader.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/SearchDataLoader.java index 3434805616f..c639f67cd5b 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/SearchDataLoader.java +++ b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/SearchDataLoader.java @@ -40,8 +40,8 @@ import org.sonar.db.qualityprofile.QualityProfileDto; import org.sonar.server.component.ComponentFinder; import org.sonar.server.qualityprofile.QProfile; import org.sonar.server.qualityprofile.QProfileFactory; -import org.sonar.server.qualityprofile.QProfileLoader; import org.sonar.server.qualityprofile.QProfileLookup; +import org.sonar.server.qualityprofile.index.ActiveRuleIndex; import org.sonarqube.ws.client.qualityprofile.SearchWsRequest; import static java.lang.String.format; @@ -50,19 +50,19 @@ import static org.sonar.server.ws.WsUtils.checkRequest; public class SearchDataLoader { private final Languages languages; private final QProfileLookup profileLookup; - private final QProfileLoader profileLoader; private final QProfileFactory profileFactory; private final DbClient dbClient; private final ComponentFinder componentFinder; + private final ActiveRuleIndex activeRuleIndex; - public SearchDataLoader(Languages languages, QProfileLookup profileLookup, QProfileLoader profileLoader, QProfileFactory profileFactory, DbClient dbClient, - ComponentFinder componentFinder) { + public SearchDataLoader(Languages languages, QProfileLookup profileLookup, QProfileFactory profileFactory, DbClient dbClient, + ComponentFinder componentFinder, ActiveRuleIndex activeRuleIndex) { this.languages = languages; this.profileLookup = profileLookup; - this.profileLoader = profileLoader; this.profileFactory = profileFactory; this.dbClient = dbClient; this.componentFinder = componentFinder; + this.activeRuleIndex = activeRuleIndex; } SearchData load(SearchWsRequest request) { @@ -70,7 +70,8 @@ public class SearchDataLoader { return new SearchData() .setProfiles(findProfiles(request)) - .setActiveRuleCountByProfileKey(profileLoader.countAllActiveRules()) + .setActiveRuleCountByProfileKey(activeRuleIndex.countAllByQualityProfileKey()) + .setActiveDeprecatedRuleCountByProfileKey(activeRuleIndex.countAllDeprecatedByQualityProfileKey()) .setProjectCountByProfileKey(dbClient.qualityProfileDao().countProjectsByProfileKey()); } diff --git a/server/sonar-server/src/main/resources/org/sonar/server/qualityprofile/ws/example-search.json b/server/sonar-server/src/main/resources/org/sonar/server/qualityprofile/ws/search-example.json similarity index 84% rename from server/sonar-server/src/main/resources/org/sonar/server/qualityprofile/ws/example-search.json rename to server/sonar-server/src/main/resources/org/sonar/server/qualityprofile/ws/search-example.json index 1a0a85588f6..2570046ee76 100644 --- a/server/sonar-server/src/main/resources/org/sonar/server/qualityprofile/ws/example-search.json +++ b/server/sonar-server/src/main/resources/org/sonar/server/qualityprofile/ws/search-example.json @@ -7,6 +7,7 @@ "languageName": "C#", "isInherited": false, "activeRuleCount": 37, + "activeDeprecatedRuleCount": 0, "isDefault": true }, { @@ -18,6 +19,7 @@ "parentKey": "my-company-profile-java-23456", "parentName": "My Company Profile", "activeRuleCount": 72, + "activeDeprecatedRuleCount": 5, "isDefault": false, "projectCount": 13 }, @@ -28,7 +30,8 @@ "languageName": "Java", "isInherited": false, "isDefault": true, - "activeRuleCount": 42 + "activeRuleCount": 42, + "activeDeprecatedRuleCount": 3 }, { "key": "sonar-way-python-01234", @@ -37,6 +40,7 @@ "languageName": "Python", "isInherited": false, "activeRuleCount": 125, + "activeDeprecatedRuleCount": 0, "isDefault": true } ] diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/QProfileServiceMediumTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/QProfileServiceMediumTest.java index 9ac80b28c96..5f9b594c724 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/QProfileServiceMediumTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/QProfileServiceMediumTest.java @@ -50,6 +50,7 @@ import org.sonar.db.user.UserDto; import org.sonar.server.activity.Activity; import org.sonar.server.activity.ActivityService; import org.sonar.server.es.SearchOptions; +import org.sonar.server.qualityprofile.index.ActiveRuleIndex; import org.sonar.server.qualityprofile.index.ActiveRuleIndexer; import org.sonar.server.rule.index.RuleIndex; import org.sonar.server.rule.index.RuleIndexDefinition; @@ -61,6 +62,9 @@ import org.sonar.server.tester.ServerTester; import org.sonar.server.tester.UserSessionRule; import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.db.rule.RuleTesting.newXooX1; +import static org.sonar.db.rule.RuleTesting.newXooX2; +import static org.sonar.db.rule.RuleTesting.newXooX3; import static org.sonar.server.qualityprofile.QProfileTesting.XOO_P1_KEY; import static org.sonar.server.qualityprofile.QProfileTesting.XOO_P2_KEY; @@ -72,31 +76,33 @@ public class QProfileServiceMediumTest { @org.junit.Rule public UserSessionRule userSessionRule = UserSessionRule.forServerTester(tester); - DbClient db; + DbClient dbClient; DbSession dbSession; QProfileService service; QProfileLoader loader; RuleActivator activator; + ActiveRuleIndex activeRuleIndex; RuleIndexer ruleIndexer; ActiveRuleIndexer activeRuleIndexer; + RuleDto xooRule1 = newXooX1().setSeverity("MINOR"); + @Before public void before() { tester.clearDbAndIndexes(); - db = tester.get(DbClient.class); - dbSession = db.openSession(false); + dbClient = tester.get(DbClient.class); + dbSession = dbClient.openSession(false); service = tester.get(QProfileService.class); loader = tester.get(QProfileLoader.class); activator = tester.get(RuleActivator.class); ruleIndexer = tester.get(RuleIndexer.class); activeRuleIndexer = tester.get(ActiveRuleIndexer.class); + activeRuleIndex = tester.get(ActiveRuleIndex.class); - // create pre-defined rules - RuleDto xooRule1 = RuleTesting.newXooX1().setSeverity("MINOR"); - db.ruleDao().insert(dbSession, xooRule1); + dbClient.ruleDao().insert(dbSession, xooRule1); // create pre-defined profiles P1 and P2 - db.qualityProfileDao().insert(dbSession, QProfileTesting.newXooP1(), QProfileTesting.newXooP2()); + dbClient.qualityProfileDao().insert(dbSession, QProfileTesting.newXooP1(), QProfileTesting.newXooP2()); dbSession.commit(); dbSession.clearCache(); @@ -123,7 +129,7 @@ public class QProfileServiceMediumTest { public void create_profile_with_xml() { userSessionRule.login().setGlobalPermissions(GlobalPermissions.QUALITY_PROFILE_ADMIN); - db.ruleDao().insert(dbSession, RuleTesting.newDto(RuleKey.of("xoo", "R1")).setLanguage("xoo").setSeverity("MINOR")); + dbClient.ruleDao().insert(dbSession, RuleTesting.newDto(RuleKey.of("xoo", "R1")).setLanguage("xoo").setSeverity("MINOR")); dbSession.commit(); ruleIndexer.index(); @@ -134,8 +140,8 @@ public class QProfileServiceMediumTest { assertThat(loader.getByKey(profile.getKey()).getLanguage()).isEqualTo("xoo"); assertThat(loader.getByKey(profile.getKey()).getName()).isEqualTo("New Profile"); - assertThat(db.activeRuleDao().selectByProfileKey(dbSession, profile.getKey())).hasSize(1); - assertThat(tester.get(RuleIndex.class).searchAll(new RuleQuery().setQProfileKey( profile.getKey()).setActivation(true))).hasSize(1); + assertThat(dbClient.activeRuleDao().selectByProfileKey(dbSession, profile.getKey())).hasSize(1); + assertThat(tester.get(RuleIndex.class).searchAll(new RuleQuery().setQProfileKey(profile.getKey()).setActivation(true))).hasSize(1); } @Test @@ -147,12 +153,38 @@ public class QProfileServiceMediumTest { dbSession.clearCache(); activeRuleIndexer.index(); - Map counts = loader.countAllActiveRules(); + Map counts = activeRuleIndex.countAllByQualityProfileKey(); assertThat(counts).hasSize(2); assertThat(counts.keySet()).containsOnly(XOO_P1_KEY, XOO_P2_KEY); assertThat(counts.values()).containsOnly(1L, 1L); } + @Test + public void count_by_all_deprecated_profiles() { + userSessionRule.login().setGlobalPermissions(GlobalPermissions.QUALITY_PROFILE_ADMIN); + + RuleDto xooRule2 = newXooX2().setStatus(RuleStatus.DEPRECATED); + RuleDto xooRule3 = newXooX3().setStatus(RuleStatus.DEPRECATED); + dbClient.ruleDao().insert(dbSession, xooRule2); + dbClient.ruleDao().insert(dbSession, xooRule3); + dbSession.commit(); + ruleIndexer.index(); + + // active some rules + service.activate(XOO_P1_KEY, new RuleActivation(xooRule1.getKey())); + service.activate(XOO_P1_KEY, new RuleActivation(xooRule2.getKey())); + service.activate(XOO_P1_KEY, new RuleActivation(xooRule3.getKey())); + service.activate(XOO_P2_KEY, new RuleActivation(xooRule1.getKey())); + service.activate(XOO_P2_KEY, new RuleActivation(xooRule3.getKey())); + dbSession.commit(); + + Map counts = activeRuleIndex.countAllDeprecatedByQualityProfileKey(); + assertThat(counts) + .hasSize(2) + .containsEntry(XOO_P1_KEY, 2L) + .containsEntry(XOO_P2_KEY, 1L); + } + @Test public void stat_for_all_profiles() { userSessionRule.login().setGlobalPermissions(GlobalPermissions.QUALITY_PROFILE_ADMIN); @@ -178,7 +210,7 @@ public class QProfileServiceMediumTest { // create deprecated rule RuleDto deprecatedXooRule = RuleTesting.newDto(RuleKey.of("xoo", "deprecated1")) .setSeverity("MINOR").setLanguage("xoo").setStatus(RuleStatus.DEPRECATED); - db.ruleDao().insert(dbSession, deprecatedXooRule); + dbClient.ruleDao().insert(dbSession, deprecatedXooRule); dbSession.commit(); ruleIndexer.index(); @@ -195,11 +227,11 @@ public class QProfileServiceMediumTest { userSessionRule.login("david").setGlobalPermissions(GlobalPermissions.QUALITY_PROFILE_ADMIN); UserDto user = new UserDto().setLogin("david").setName("David").setEmail("dav@id.com").setCreatedAt(System.currentTimeMillis()).setUpdatedAt(System.currentTimeMillis()); - db.userDao().insert(dbSession, user); + dbClient.userDao().insert(dbSession, user); dbSession.commit(); // We need an actual rule in DB to test RuleName in Activity - RuleDto rule = db.ruleDao().selectOrFailByKey(dbSession, RuleTesting.XOO_X1); + RuleDto rule = dbClient.ruleDao().selectOrFailByKey(dbSession, RuleTesting.XOO_X1); tester.get(ActivityService.class).save(ActiveRuleChange.createFor(ActiveRuleChange.Type.ACTIVATED, ActiveRuleKey.of(XOO_P1_KEY, RuleTesting.XOO_X1)) .setSeverity(Severity.MAJOR) diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/SearchActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/SearchActionTest.java index 5791fe931b3..e541884328f 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/SearchActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/SearchActionTest.java @@ -39,8 +39,8 @@ import org.sonar.db.qualityprofile.QualityProfileDto; import org.sonar.server.component.ComponentFinder; import org.sonar.server.language.LanguageTesting; import org.sonar.server.qualityprofile.QProfileFactory; -import org.sonar.server.qualityprofile.QProfileLoader; import org.sonar.server.qualityprofile.QProfileLookup; +import org.sonar.server.qualityprofile.index.ActiveRuleIndex; import org.sonar.server.ws.WsActionTester; import static org.assertj.core.api.Assertions.assertThat; @@ -52,7 +52,6 @@ import static org.sonar.server.qualityprofile.ws.SearchAction.PARAM_PROFILE_NAME import static org.sonar.server.qualityprofile.ws.SearchAction.PARAM_PROJECT_KEY; import static org.sonar.test.JsonAssert.assertJson; - public class SearchActionTest { @Rule @@ -61,8 +60,7 @@ public class SearchActionTest { @Rule public ExpectedException expectedException = ExpectedException.none(); - // TODO remove mock - private QProfileLoader profileLoader = mock(QProfileLoader.class); + private ActiveRuleIndex activeRuleIndex = mock(ActiveRuleIndex.class); final DbClient dbClient = db.getDbClient(); final DbSession dbSession = db.getSession(); @@ -87,26 +85,26 @@ public class SearchActionTest { new SearchDataLoader( languages, new QProfileLookup(dbClient), - profileLoader, new QProfileFactory(dbClient), dbClient, - new ComponentFinder(dbClient)), + new ComponentFinder(dbClient), activeRuleIndex), languages)); } @Test public void search_nominal() throws Exception { - when(profileLoader.countAllActiveRules()).thenReturn(ImmutableMap.of( + when(activeRuleIndex.countAllByQualityProfileKey()).thenReturn(ImmutableMap.of( "sonar-way-xoo1-12345", 11L, - "my-sonar-way-xoo2-34567", 33L - )); + "my-sonar-way-xoo2-34567", 33L)); + when(activeRuleIndex.countAllDeprecatedByQualityProfileKey()).thenReturn(ImmutableMap.of( + "sonar-way-xoo1-12345", 1L, + "my-sonar-way-xoo2-34567", 2L)); qualityProfileDao.insert(dbSession, QualityProfileDto.createFor("sonar-way-xoo1-12345").setLanguage(xoo1.getKey()).setName("Sonar way").setDefault(true), QualityProfileDto.createFor("sonar-way-xoo2-23456").setLanguage(xoo2.getKey()).setName("Sonar way"), QualityProfileDto.createFor("my-sonar-way-xoo2-34567").setLanguage(xoo2.getKey()).setName("My Sonar way").setParentKee("sonar-way-xoo2-23456"), - QualityProfileDto.createFor("sonar-way-other-666").setLanguage("other").setName("Sonar way").setDefault(true) - ); + QualityProfileDto.createFor("sonar-way-other-666").setLanguage("other").setName("Sonar way").setDefault(true)); new ComponentDao().insert(dbSession, newProjectDto("project-uuid1"), newProjectDto("project-uuid2")); @@ -122,8 +120,7 @@ public class SearchActionTest { @Test public void search_for_language() throws Exception { qualityProfileDao.insert(dbSession, - QualityProfileDto.createFor("sonar-way-xoo1-12345").setLanguage(xoo1.getKey()).setName("Sonar way") - ); + QualityProfileDto.createFor("sonar-way-xoo1-12345").setLanguage(xoo1.getKey()).setName("Sonar way")); commit(); String result = ws.newRequest().setParam("language", xoo1.getKey()).execute().getInput(); diff --git a/server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/ws/SearchActionTest/search.json b/server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/ws/SearchActionTest/search.json index 6c051e7ac96..8874c784840 100644 --- a/server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/ws/SearchActionTest/search.json +++ b/server/sonar-server/src/test/resources/org/sonar/server/qualityprofile/ws/SearchActionTest/search.json @@ -7,7 +7,8 @@ "languageName": "Xoo1", "isInherited": false, "isDefault": true, - "activeRuleCount": 11 + "activeRuleCount": 11, + "activeDeprecatedRuleCount":1 }, { "key": "my-sonar-way-xoo2-34567", @@ -19,6 +20,7 @@ "parentKey": "sonar-way-xoo2-23456", "parentName": "Sonar way", "activeRuleCount": 33, + "activeDeprecatedRuleCount": 2, "projectCount": 0 }, { @@ -29,7 +31,8 @@ "isInherited": false, "isDefault": false, "activeRuleCount": 0, + "activeDeprecatedRuleCount": 0, "projectCount": 2 } ] -} \ No newline at end of file +} diff --git a/sonar-ws/src/main/protobuf/ws-qualityprofiles.proto b/sonar-ws/src/main/protobuf/ws-qualityprofiles.proto index 7ec0752759a..9b4e871cb51 100644 --- a/sonar-ws/src/main/protobuf/ws-qualityprofiles.proto +++ b/sonar-ws/src/main/protobuf/ws-qualityprofiles.proto @@ -38,6 +38,7 @@ message SearchWsResponse { optional string parentName = 7; optional bool isDefault = 8; optional int64 activeRuleCount = 9; + optional int64 activeDeprecatedRuleCount = 12; optional int64 projectCount = 10; optional string rulesUpdatedAt = 11; } -- 2.39.5