import org.sonar.core.util.UuidFactoryFast; | import org.sonar.core.util.UuidFactoryFast; | ||||
import org.sonar.db.DbSession; | import org.sonar.db.DbSession; | ||||
import org.sonar.db.DbTester; | import org.sonar.db.DbTester; | ||||
import org.sonar.db.Pagination; | |||||
import org.sonar.db.RowNotFoundException; | import org.sonar.db.RowNotFoundException; | ||||
import org.sonar.db.issue.ImpactDto; | import org.sonar.db.issue.ImpactDto; | ||||
import org.sonar.db.qualityprofile.QProfileDto; | |||||
import org.sonar.db.rule.RuleDto.Scope; | import org.sonar.db.rule.RuleDto.Scope; | ||||
import static com.google.common.collect.Sets.newHashSet; | import static com.google.common.collect.Sets.newHashSet; | ||||
import static org.sonar.api.issue.impact.SoftwareQuality.MAINTAINABILITY; | import static org.sonar.api.issue.impact.SoftwareQuality.MAINTAINABILITY; | ||||
import static org.sonar.api.issue.impact.SoftwareQuality.RELIABILITY; | import static org.sonar.api.issue.impact.SoftwareQuality.RELIABILITY; | ||||
import static org.sonar.api.issue.impact.SoftwareQuality.SECURITY; | import static org.sonar.api.issue.impact.SoftwareQuality.SECURITY; | ||||
import static org.sonar.api.rule.RuleStatus.*; | |||||
import static org.sonar.api.rule.RuleStatus.DEPRECATED; | import static org.sonar.api.rule.RuleStatus.DEPRECATED; | ||||
import static org.sonar.api.rule.RuleStatus.READY; | |||||
import static org.sonar.api.rule.RuleStatus.REMOVED; | import static org.sonar.api.rule.RuleStatus.REMOVED; | ||||
import static org.sonar.db.Pagination.forPage; | import static org.sonar.db.Pagination.forPage; | ||||
import static org.sonar.db.rule.RuleListQuery.RuleListQueryBuilder.newRuleListQueryBuilder; | |||||
public class RuleDaoIT { | public class RuleDaoIT { | ||||
private static final String UNKNOWN_RULE_UUID = "unknown-uuid"; | private static final String UNKNOWN_RULE_UUID = "unknown-uuid"; | ||||
.isInstanceOf(PersistenceException.class); | .isInstanceOf(PersistenceException.class); | ||||
} | } | ||||
@Test | |||||
public void selectRules_whenPagination_shouldReturnSpecificPage() { | |||||
db.rules().insert(rule -> rule.setUuid("uuid_1")); | |||||
db.rules().insert(rule -> rule.setUuid("uuid_2")); | |||||
db.rules().insert(rule -> rule.setUuid("uuid_3")); | |||||
db.rules().insert(rule -> rule.setUuid("uuid_4")); | |||||
Pagination pagination = Pagination.forPage(1).andSize(2); | |||||
RuleListResult ruleListResult = underTest.selectRules(db.getSession(), newRuleListQueryBuilder().build(), pagination); | |||||
assertThat(ruleListResult.getTotal()).isEqualTo(4); | |||||
assertThat(ruleListResult.getUuids()).containsExactly("uuid_1", "uuid_2"); | |||||
} | |||||
@Test | |||||
public void selectRules_whenQueriedWithCreatedAt_shouldReturnRulesCreatedAfterCorrectlySorted() { | |||||
long baseTime = 1696028921248L; | |||||
db.rules().insert(rule -> rule.setUuid("uuid_1").setCreatedAt(baseTime)); | |||||
db.rules().insert(rule -> rule.setUuid("uuid_2").setCreatedAt(baseTime + 1000)); | |||||
db.rules().insert(rule -> rule.setUuid("uuid_3").setCreatedAt(baseTime - 2000)); | |||||
db.rules().insert(rule -> rule.setUuid("uuid_4").setCreatedAt(baseTime - 1)); | |||||
db.rules().insert(rule -> rule.setUuid("uuid_5").setCreatedAt(baseTime + 1)); | |||||
RuleListQuery ruleListQuery = newRuleListQueryBuilder().createdAt(baseTime).sortField("createdAt").sortDirection("desc").build(); | |||||
RuleListResult ruleListResult = underTest.selectRules(db.getSession(), ruleListQuery, Pagination.all()); | |||||
assertThat(ruleListResult.getUuids()).containsExactly("uuid_2", "uuid_5", "uuid_1"); | |||||
assertThat(ruleListResult.getTotal()).isEqualTo(3); | |||||
} | |||||
@Test | |||||
public void selectRules_whenQueriedWithLanguage_shouldReturnOnlyRulesWithLanguage() { | |||||
String queriedLang = "java"; | |||||
db.rules().insert(rule -> rule.setUuid("uuid_1").setLanguage(queriedLang)); | |||||
db.rules().insert(rule -> rule.setUuid("uuid_2").setLanguage("cpp")); | |||||
db.rules().insert(rule -> rule.setUuid("uuid_3").setLanguage("js")); | |||||
db.rules().insert(rule -> rule.setUuid("uuid_4").setLanguage(queriedLang)); | |||||
RuleListQuery ruleListQuery = newRuleListQueryBuilder().language(queriedLang).build(); | |||||
RuleListResult ruleListResult = underTest.selectRules(db.getSession(), ruleListQuery, Pagination.all()); | |||||
assertThat(ruleListResult.getUuids()).containsExactly("uuid_1", "uuid_4"); | |||||
assertThat(ruleListResult.getTotal()).isEqualTo(2); | |||||
} | |||||
@Test | |||||
public void selectRules_whenQueriedWithQualityProfile_shouldReturnOnlyRulesActiveForThisQualityProfile() { | |||||
QProfileDto profile = db.qualityProfiles().insert(); | |||||
RuleDto rule1 = db.rules().insert(rule -> rule.setUuid("uuid_1")); | |||||
db.rules().insert(rule -> rule.setUuid("uuid_2")); | |||||
RuleDto rule3 = db.rules().insert(rule -> rule.setUuid("uuid_3")); | |||||
db.rules().insert(rule -> rule.setUuid("uuid_4")); | |||||
db.qualityProfiles().activateRule(profile, rule1); | |||||
db.qualityProfiles().activateRule(profile, rule3); | |||||
RuleListQuery ruleListQuery = newRuleListQueryBuilder().profileUuid(profile.getRulesProfileUuid()).build(); | |||||
RuleListResult ruleListResult = underTest.selectRules(db.getSession(), ruleListQuery, Pagination.all()); | |||||
assertThat(ruleListResult.getUuids()).containsExactly("uuid_1", "uuid_3"); | |||||
assertThat(ruleListResult.getTotal()).isEqualTo(2); | |||||
} | |||||
private static ImpactDto newRuleDefaultImpact(SoftwareQuality softwareQuality, org.sonar.api.issue.impact.Severity severity) { | private static ImpactDto newRuleDefaultImpact(SoftwareQuality softwareQuality, org.sonar.api.issue.impact.Severity severity) { | ||||
return new ImpactDto() | return new ImpactDto() | ||||
.setUuid(UuidFactoryFast.getInstance().create()) | .setUuid(UuidFactoryFast.getInstance().create()) |
private static String toLowerCaseAndSurroundWithPercentSigns(@Nullable String query) { | private static String toLowerCaseAndSurroundWithPercentSigns(@Nullable String query) { | ||||
return isBlank(query) ? PERCENT_SIGN : (PERCENT_SIGN + query.toLowerCase(Locale.ENGLISH) + PERCENT_SIGN); | return isBlank(query) ? PERCENT_SIGN : (PERCENT_SIGN + query.toLowerCase(Locale.ENGLISH) + PERCENT_SIGN); | ||||
} | } | ||||
public RuleListResult selectRules(DbSession dbSession, RuleListQuery ruleListQuery, Pagination pagination) { | |||||
return new RuleListResult( | |||||
mapper(dbSession).selectRules(ruleListQuery, pagination), | |||||
mapper(dbSession).countByQuery(ruleListQuery)); | |||||
} | |||||
} | } |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2023 SonarSource SA | |||||
* mailto:info AT sonarsource DOT com | |||||
* | |||||
* This program is free software; you can redistribute it and/or | |||||
* modify it under the terms of the GNU Lesser General Public | |||||
* License as published by the Free Software Foundation; either | |||||
* version 3 of the License, or (at your option) any later version. | |||||
* | |||||
* This program is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||||
* Lesser General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU Lesser General Public License | |||||
* along with this program; if not, write to the Free Software Foundation, | |||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||||
*/ | |||||
package org.sonar.db.rule; | |||||
import javax.annotation.Nullable; | |||||
public class RuleListQuery { | |||||
private final Long createdAt; | |||||
private final String language; | |||||
private final String profileUuid; | |||||
private final String sortField; | |||||
private final String sortDirection; | |||||
private RuleListQuery(Long createdAt, String language, String profileUuid, String sortField, String sortDirection) { | |||||
this.createdAt = createdAt; | |||||
this.language = language; | |||||
this.profileUuid = profileUuid; | |||||
this.sortField = sortField; | |||||
this.sortDirection = sortDirection; | |||||
} | |||||
public Long getCreatedAt() { | |||||
return createdAt; | |||||
} | |||||
public String getLanguage() { | |||||
return language; | |||||
} | |||||
public String getProfileUuid() { | |||||
return profileUuid; | |||||
} | |||||
public String getSortField() { | |||||
return sortField; | |||||
} | |||||
public String getSortDirection() { | |||||
return sortDirection; | |||||
} | |||||
public static final class RuleListQueryBuilder { | |||||
private Long createdAt; | |||||
private String language; | |||||
private String profileUuid; | |||||
private String sortField; | |||||
private String sortDirection = "asc"; | |||||
private RuleListQueryBuilder() { | |||||
} | |||||
public static RuleListQueryBuilder newRuleListQueryBuilder() { | |||||
return new RuleListQueryBuilder(); | |||||
} | |||||
public RuleListQueryBuilder createdAt(@Nullable Long createdAt) { | |||||
this.createdAt = createdAt; | |||||
return this; | |||||
} | |||||
public RuleListQueryBuilder language(String language) { | |||||
this.language = language; | |||||
return this; | |||||
} | |||||
public RuleListQueryBuilder profileUuid(@Nullable String profileUuid) { | |||||
this.profileUuid = profileUuid; | |||||
return this; | |||||
} | |||||
public RuleListQueryBuilder sortField(String sortField) { | |||||
this.sortField = sortField; | |||||
return this; | |||||
} | |||||
public RuleListQueryBuilder sortDirection(String sortDirection) { | |||||
this.sortDirection = sortDirection; | |||||
return this; | |||||
} | |||||
public RuleListQuery build() { | |||||
return new RuleListQuery(createdAt, language, profileUuid, sortField, sortDirection); | |||||
} | |||||
} | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2023 SonarSource SA | |||||
* mailto:info AT sonarsource DOT com | |||||
* | |||||
* This program is free software; you can redistribute it and/or | |||||
* modify it under the terms of the GNU Lesser General Public | |||||
* License as published by the Free Software Foundation; either | |||||
* version 3 of the License, or (at your option) any later version. | |||||
* | |||||
* This program is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||||
* Lesser General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU Lesser General Public License | |||||
* along with this program; if not, write to the Free Software Foundation, | |||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||||
*/ | |||||
package org.sonar.db.rule; | |||||
import java.util.List; | |||||
public class RuleListResult { | |||||
private final List<String> uuids; | |||||
private final long total; | |||||
public RuleListResult(List<String> uuids, long total) { | |||||
this.uuids = uuids; | |||||
this.total = total; | |||||
} | |||||
public List<String> getUuids() { | |||||
return uuids; | |||||
} | |||||
public long getTotal() { | |||||
return total; | |||||
} | |||||
} |
void deleteRuleTags(String ruleUuid); | void deleteRuleTags(String ruleUuid); | ||||
List<String> selectTags(@Param("query") String query, @Param("pagination") Pagination pagination); | List<String> selectTags(@Param("query") String query, @Param("pagination") Pagination pagination); | ||||
List<String> selectRules(@Param("query") RuleListQuery query, @Param("pagination") Pagination pagination); | |||||
Long countByQuery(@Param("query") RuleListQuery query); | |||||
} | } |
<include refid="org.sonar.db.common.Common.pagination"/> | <include refid="org.sonar.db.common.Common.pagination"/> | ||||
</select> | </select> | ||||
<select id="selectRules"> | |||||
select r.uuid from rules r | |||||
<include refid="queryList"/> | |||||
<choose> | |||||
<when test="query.sortField == 'createdAt'"> | |||||
order by r.created_at ${query.sortDirection} | |||||
</when> | |||||
<otherwise> | |||||
order by r.uuid | |||||
</otherwise> | |||||
</choose> | |||||
<include refid="org.sonar.db.common.Common.pagination"/> | |||||
</select> | |||||
<select id="countByQuery" resultType="long"> | |||||
select count(r.uuid) from rules r | |||||
<include refid="queryList"/> | |||||
</select> | |||||
<sql id="queryList"> | |||||
<if test="query.profileUuid != null"> | |||||
inner join active_rules ar on ar.rule_uuid = r.uuid | |||||
</if> | |||||
where | |||||
r.status != 'REMOVED' | |||||
and r.is_external = ${_false} | |||||
<if test="query.createdAt != null"> | |||||
and r.created_at >= #{query.createdAt,jdbcType=BIGINT} | |||||
</if> | |||||
<if test="query.language != null"> | |||||
and r.language = #{query.language,jdbcType=VARCHAR} | |||||
</if> | |||||
<if test="query.profileUuid != null"> | |||||
and ar.profile_uuid = #{query.profileUuid,jdbcType=VARCHAR} | |||||
</if> | |||||
</sql> | |||||
</mapper> | </mapper> | ||||
searchUsers, | searchUsers, | ||||
setDefaultProfile, | setDefaultProfile, | ||||
} from '../quality-profiles'; | } from '../quality-profiles'; | ||||
import { getRuleDetails, searchRules } from '../rules'; | |||||
import { getRuleDetails, searchRules, listRules } from '../rules'; | |||||
jest.mock('../../api/rules'); | jest.mock('../../api/rules'); | ||||
jest.mocked(copyProfile).mockImplementation(this.handleCopyProfile); | jest.mocked(copyProfile).mockImplementation(this.handleCopyProfile); | ||||
jest.mocked(getImporters).mockImplementation(this.handleGetImporters); | jest.mocked(getImporters).mockImplementation(this.handleGetImporters); | ||||
jest.mocked(searchRules).mockImplementation(this.handleSearchRules); | jest.mocked(searchRules).mockImplementation(this.handleSearchRules); | ||||
jest.mocked(listRules).mockImplementation(this.handleListRules); | |||||
jest.mocked(compareProfiles).mockImplementation(this.handleCompareQualityProfiles); | jest.mocked(compareProfiles).mockImplementation(this.handleCompareQualityProfiles); | ||||
jest.mocked(activateRule).mockImplementation(this.handleActivateRule); | jest.mocked(activateRule).mockImplementation(this.handleActivateRule); | ||||
jest.mocked(deactivateRule).mockImplementation(this.handleDeactivateRule); | jest.mocked(deactivateRule).mockImplementation(this.handleDeactivateRule); | ||||
return this.reply(this.searchRulesResponse); | return this.reply(this.searchRulesResponse); | ||||
}; | }; | ||||
handleListRules = (data: SearchRulesQuery): Promise<SearchRulesResponse> => { | |||||
// Both APIs are mocked the same way, this method is only here to make it explicit. | |||||
return this.handleSearchRules(data); | |||||
}; | |||||
handleGetQualityProfile = () => { | handleGetQualityProfile = () => { | ||||
return this.reply({ | return this.reply({ | ||||
profile: mockQualityProfile(), | profile: mockQualityProfile(), |
return getJSON('/api/rules/search', data).catch(throwGlobalError); | return getJSON('/api/rules/search', data).catch(throwGlobalError); | ||||
} | } | ||||
export function listRules(data: SearchRulesQuery): Promise<SearchRulesResponse> { | |||||
return getJSON('/api/rules/list', data).catch(throwGlobalError); | |||||
} | |||||
export function getRuleRepositories(parameters: { | export function getRuleRepositories(parameters: { | ||||
q: string; | q: string; | ||||
}): Promise<Array<{ key: string; language: string; name: string }>> { | }): Promise<Array<{ key: string; language: string; name: string }>> { |
import { noop, sortBy } from 'lodash'; | import { noop, sortBy } from 'lodash'; | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { useIntl } from 'react-intl'; | import { useIntl } from 'react-intl'; | ||||
import { searchRules } from '../../../api/rules'; | |||||
import { listRules } from '../../../api/rules'; | |||||
import { toShortISO8601String } from '../../../helpers/dates'; | import { toShortISO8601String } from '../../../helpers/dates'; | ||||
import { translateWithParameters } from '../../../helpers/l10n'; | import { translateWithParameters } from '../../../helpers/l10n'; | ||||
import { formatMeasure } from '../../../helpers/measures'; | import { formatMeasure } from '../../../helpers/measures'; | ||||
s: 'createdAt', | s: 'createdAt', | ||||
}; | }; | ||||
searchRules(data).then(({ actives, rules, paging: { total } }) => { | |||||
listRules(data).then(({ actives, rules, paging: { total } }) => { | |||||
setLatestRules(sortBy(parseRules(rules, actives), 'langName')); | setLatestRules(sortBy(parseRules(rules, actives), 'langName')); | ||||
setLatestRulesTotal(total); | setLatestRulesTotal(total); | ||||
}, noop); | }, noop); |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2023 SonarSource SA | |||||
* mailto:info AT sonarsource DOT com | |||||
* | |||||
* This program is free software; you can redistribute it and/or | |||||
* modify it under the terms of the GNU Lesser General Public | |||||
* License as published by the Free Software Foundation; either | |||||
* version 3 of the License, or (at your option) any later version. | |||||
* | |||||
* This program is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||||
* Lesser General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU Lesser General Public License | |||||
* along with this program; if not, write to the Free Software Foundation, | |||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||||
*/ | |||||
package org.sonar.server.rule.ws; | |||||
import java.util.List; | |||||
import org.junit.Rule; | |||||
import org.junit.Test; | |||||
import org.sonar.api.resources.Languages; | |||||
import org.sonar.db.DbTester; | |||||
import org.sonar.db.qualityprofile.ActiveRuleDto; | |||||
import org.sonar.db.qualityprofile.QProfileDto; | |||||
import org.sonar.db.rule.RuleDto; | |||||
import org.sonarqube.ws.Rules; | |||||
import static org.assertj.core.api.Assertions.assertThat; | |||||
public class ActiveRuleCompleterIT { | |||||
@Rule | |||||
public DbTester dbTester = DbTester.create(); | |||||
@Test | |||||
public void test_completeShow() { | |||||
ActiveRuleCompleter underTest = new ActiveRuleCompleter(dbTester.getDbClient(), new Languages()); | |||||
RuleDto rule = dbTester.rules().insert(); | |||||
QProfileDto qualityProfile = dbTester.qualityProfiles().insert(); | |||||
ActiveRuleDto activeRule = dbTester.qualityProfiles().activateRule(qualityProfile, rule); | |||||
List<Rules.Active> result = underTest.completeShow(dbTester.getSession(), rule); | |||||
assertThat(result).extracting(Rules.Active::getQProfile).containsExactlyInAnyOrder(qualityProfile.getKee()); | |||||
assertThat(result).extracting(Rules.Active::getSeverity).containsExactlyInAnyOrder(activeRule.getSeverityString()); | |||||
} | |||||
} |
*/ | */ | ||||
package org.sonar.server.rule.ws; | package org.sonar.server.rule.ws; | ||||
import java.text.ParseException; | |||||
import java.text.SimpleDateFormat; | |||||
import java.util.Map; | |||||
import java.util.Optional; | import java.util.Optional; | ||||
import org.junit.Rule; | import org.junit.Rule; | ||||
import org.junit.Test; | import org.junit.Test; | ||||
import org.sonar.api.resources.Languages; | |||||
import org.sonar.api.rule.RuleKey; | import org.sonar.api.rule.RuleKey; | ||||
import org.sonar.api.server.ws.WebService; | import org.sonar.api.server.ws.WebService; | ||||
import org.sonar.api.utils.System2; | import org.sonar.api.utils.System2; | ||||
import org.sonar.db.DbTester; | import org.sonar.db.DbTester; | ||||
import org.sonar.db.qualityprofile.QProfileDto; | |||||
import org.sonar.db.rule.RuleDto; | |||||
import org.sonar.db.rule.RuleTesting; | import org.sonar.db.rule.RuleTesting; | ||||
import org.sonar.server.exceptions.NotFoundException; | |||||
import org.sonar.server.language.LanguageTesting; | |||||
import org.sonar.server.rule.RuleDescriptionFormatter; | |||||
import org.sonar.server.tester.UserSessionRule; | |||||
import org.sonar.server.text.MacroInterpreter; | |||||
import org.sonar.server.ws.TestRequest; | |||||
import org.sonar.server.ws.WsActionTester; | import org.sonar.server.ws.WsActionTester; | ||||
import org.sonarqube.ws.Rules; | import org.sonarqube.ws.Rules; | ||||
import static org.assertj.core.api.Assertions.assertThat; | import static org.assertj.core.api.Assertions.assertThat; | ||||
import static org.assertj.core.api.Assertions.assertThatThrownBy; | |||||
import static org.mockito.Mockito.mock; | |||||
public class ListActionIT { | public class ListActionIT { | ||||
private static final String RULE_KEY_1 = "S001"; | |||||
private static final String RULE_KEY_2 = "S002"; | |||||
private static final String RULE_KEY_1 = "java:S001"; | |||||
private static final String RULE_KEY_2 = "java:S002"; | |||||
@Rule | @Rule | ||||
public DbTester dbTester = DbTester.create(System2.INSTANCE); | |||||
public DbTester db = DbTester.create(System2.INSTANCE); | |||||
ListAction underTest = new ListAction(dbTester.getDbClient()); | |||||
@org.junit.Rule | |||||
public UserSessionRule userSession = UserSessionRule.standalone(); | |||||
WsActionTester tester = new WsActionTester(underTest); | |||||
private final Languages languages = LanguageTesting.newLanguages("java", "js"); | |||||
private final MacroInterpreter macroInterpreter = mock(MacroInterpreter.class); | |||||
private final RuleMapper ruleMapper = new RuleMapper(languages, macroInterpreter, new RuleDescriptionFormatter()); | |||||
private final RuleWsSupport ruleWsSupport = new RuleWsSupport(db.getDbClient(), userSession); | |||||
private final RulesResponseFormatter rulesResponseFormatter = new RulesResponseFormatter(db.getDbClient(), ruleWsSupport, ruleMapper, languages); | |||||
private final WsActionTester ws = new WsActionTester(new ListAction(db.getDbClient(), rulesResponseFormatter)); | |||||
@Test | @Test | ||||
public void define() { | |||||
WebService.Action def = tester.getDef(); | |||||
assertThat(def.params()).isEmpty(); | |||||
public void define_shouldDefineParameters() { | |||||
WebService.Action def = ws.getDef(); | |||||
assertThat(def.params()).extracting(WebService.Param::key) | |||||
.containsExactlyInAnyOrder("asc", "p", "s", "ps", "available_since", "qprofile"); | |||||
} | } | ||||
@Test | @Test | ||||
public void return_rules_in_protobuf() { | |||||
dbTester.rules().insert(RuleTesting.newRule(RuleKey.of("java", RULE_KEY_1)).setConfigKey(null).setName(null)); | |||||
dbTester.rules().insert(RuleTesting.newRule(RuleKey.of("java", RULE_KEY_2)).setConfigKey("I002").setName("Rule Two")); | |||||
dbTester.getSession().commit(); | |||||
public void execute_shouldReturnRules() { | |||||
db.rules().insert(RuleTesting.newRule(RuleKey.parse(RULE_KEY_1)).setConfigKey(null).setName(null)); | |||||
db.rules().insert(RuleTesting.newRule(RuleKey.parse(RULE_KEY_2)).setConfigKey("I002").setName("Rule Two")); | |||||
db.getSession().commit(); | |||||
Rules.ListResponse listResponse = tester.newRequest() | |||||
Rules.ListResponse listResponse = ws.newRequest() | |||||
.executeProtobuf(Rules.ListResponse.class); | .executeProtobuf(Rules.ListResponse.class); | ||||
assertThat(listResponse.getRulesCount()).isEqualTo(2); | assertThat(listResponse.getRulesCount()).isEqualTo(2); | ||||
Rules.ListResponse.Rule ruleS001 = getRule(listResponse, RULE_KEY_1); | |||||
Rules.Rule ruleS001 = getRule(listResponse, RULE_KEY_1); | |||||
assertThat(ruleS001.getKey()).isEqualTo(RULE_KEY_1); | assertThat(ruleS001.getKey()).isEqualTo(RULE_KEY_1); | ||||
assertThat(ruleS001.getInternalKey()).isEmpty(); | assertThat(ruleS001.getInternalKey()).isEmpty(); | ||||
assertThat(ruleS001.getName()).isEmpty(); | assertThat(ruleS001.getName()).isEmpty(); | ||||
Rules.ListResponse.Rule ruleS002 = getRule(listResponse, RULE_KEY_2); | |||||
Rules.Rule ruleS002 = getRule(listResponse, RULE_KEY_2); | |||||
assertThat(ruleS002.getKey()).isEqualTo(RULE_KEY_2); | assertThat(ruleS002.getKey()).isEqualTo(RULE_KEY_2); | ||||
assertThat(ruleS002.getInternalKey()).isEqualTo("I002"); | assertThat(ruleS002.getInternalKey()).isEqualTo("I002"); | ||||
assertThat(ruleS002.getName()).isEqualTo("Rule Two"); | assertThat(ruleS002.getName()).isEqualTo("Rule Two"); | ||||
} | } | ||||
private Rules.ListResponse.Rule getRule(Rules.ListResponse listResponse, String ruleKey) { | |||||
Optional<Rules.ListResponse.Rule> rule = listResponse.getRulesList().stream() | |||||
@Test | |||||
public void execute_shouldReturnFilteredRules_whenQProfileDefined() { | |||||
QProfileDto profile = db.qualityProfiles().insert(p -> p.setLanguage("java")); | |||||
RuleDto rule = db.rules().insert(r -> r.setRuleKey(RuleKey.parse(RULE_KEY_1))); | |||||
db.qualityProfiles().activateRule(profile, rule); | |||||
Rules.ListResponse result = ws.newRequest() | |||||
.setParam("qprofile", profile.getKee()) | |||||
.executeProtobuf(Rules.ListResponse.class); | |||||
assertThat(result.getPaging().getTotal()).isOne(); | |||||
assertThat(result.getPaging().getPageIndex()).isOne(); | |||||
assertThat(result.getRulesCount()).isOne(); | |||||
assertThat(result.getActives()).isNotNull(); | |||||
Map<String, Rules.ActiveList> activeRules = result.getActives().getActivesMap(); | |||||
assertThat(activeRules.get(rule.getKey().toString())).isNotNull(); | |||||
assertThat(activeRules.get(rule.getKey().toString()).getActiveListList()).hasSize(1); | |||||
} | |||||
@Test | |||||
public void execute_shouldReturnFilteredRules_whenAvailableSinceDefined() throws ParseException { | |||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); | |||||
long recentDay = dateFormat.parse("2023-02-20").getTime(); | |||||
long oldDay = dateFormat.parse("2022-09-17").getTime(); | |||||
db.rules().insert(r -> r.setRuleKey(RuleKey.parse(RULE_KEY_1)).setCreatedAt(recentDay)); | |||||
db.rules().insert(r -> r.setRuleKey(RuleKey.parse(RULE_KEY_2)).setCreatedAt(oldDay)); | |||||
Rules.ListResponse result = ws.newRequest() | |||||
.setParam("available_since", "2022-11-23") | |||||
.executeProtobuf(Rules.ListResponse.class); | |||||
assertThat(result.getRulesCount()).isOne(); | |||||
assertThat(result.getRulesList().stream().map(Rules.Rule::getKey)).containsOnly(RULE_KEY_1); | |||||
} | |||||
@Test | |||||
public void execute_shouldFailWithNotFoundException_whenQProfileDoesNotExist() { | |||||
String unknownProfile = "unknown_profile"; | |||||
TestRequest request = ws.newRequest() | |||||
.setParam("qprofile", unknownProfile); | |||||
assertThatThrownBy(() -> request.executeProtobuf(Rules.SearchResponse.class)) | |||||
.isInstanceOf(NotFoundException.class) | |||||
.hasMessage("The specified qualityProfile '" + unknownProfile + "' does not exist"); | |||||
} | |||||
private Rules.Rule getRule(Rules.ListResponse listResponse, String ruleKey) { | |||||
Optional<Rules.Rule> rule = listResponse.getRulesList().stream() | |||||
.filter(r -> ruleKey.equals(r.getKey())) | .filter(r -> ruleKey.equals(r.getKey())) | ||||
.findFirst(); | .findFirst(); | ||||
assertThat(rule).isPresent(); | assertThat(rule).isPresent(); |
private final RuleIndexer ruleIndexer = new RuleIndexer(es.client(), db.getDbClient()); | private final RuleIndexer ruleIndexer = new RuleIndexer(es.client(), db.getDbClient()); | ||||
private final ActiveRuleIndexer activeRuleIndexer = new ActiveRuleIndexer(db.getDbClient(), es.client()); | private final ActiveRuleIndexer activeRuleIndexer = new ActiveRuleIndexer(db.getDbClient(), es.client()); | ||||
private final Languages languages = LanguageTesting.newLanguages(JAVA, "js"); | private final Languages languages = LanguageTesting.newLanguages(JAVA, "js"); | ||||
private final ActiveRuleCompleter activeRuleCompleter = new ActiveRuleCompleter(db.getDbClient(), languages); | |||||
private final RuleQueryFactory ruleQueryFactory = new RuleQueryFactory(db.getDbClient()); | private final RuleQueryFactory ruleQueryFactory = new RuleQueryFactory(db.getDbClient()); | ||||
private final MacroInterpreter macroInterpreter = mock(MacroInterpreter.class); | private final MacroInterpreter macroInterpreter = mock(MacroInterpreter.class); | ||||
private final QualityProfileChangeEventService qualityProfileChangeEventService = mock(QualityProfileChangeEventService.class); | private final QualityProfileChangeEventService qualityProfileChangeEventService = mock(QualityProfileChangeEventService.class); | ||||
private final RuleMapper ruleMapper = new RuleMapper(languages, macroInterpreter, new RuleDescriptionFormatter()); | private final RuleMapper ruleMapper = new RuleMapper(languages, macroInterpreter, new RuleDescriptionFormatter()); | ||||
private final SearchAction underTest = new SearchAction(ruleIndex, activeRuleCompleter, ruleQueryFactory, db.getDbClient(), ruleMapper, | |||||
new RuleWsSupport(db.getDbClient(), userSession)); | |||||
private final SearchAction underTest = new SearchAction(ruleIndex, ruleQueryFactory, db.getDbClient(), | |||||
new RulesResponseFormatter(db.getDbClient(), new RuleWsSupport(db.getDbClient(), userSession), ruleMapper, languages)); | |||||
private final TypeValidations typeValidations = new TypeValidations(asList(new StringTypeValidation(), new IntegerTypeValidation())); | private final TypeValidations typeValidations = new TypeValidations(asList(new StringTypeValidation(), new IntegerTypeValidation())); | ||||
private final SonarQubeVersion sonarQubeVersion = new SonarQubeVersion(Version.create(10, 3)); | private final SonarQubeVersion sonarQubeVersion = new SonarQubeVersion(Version.create(10, 3)); | ||||
private final RuleActivator ruleActivator = new RuleActivator(System2.INSTANCE, db.getDbClient(), typeValidations, userSession, | private final RuleActivator ruleActivator = new RuleActivator(System2.INSTANCE, db.getDbClient(), typeValidations, userSession, | ||||
assertThat(result.getFacets().getFacets(0).getValuesList()) | assertThat(result.getFacets().getFacets(0).getValuesList()) | ||||
.extracting(v -> entry(v.getVal(), v.getCount())).contains( | .extracting(v -> entry(v.getVal(), v.getCount())).contains( | ||||
entry(CleanCodeAttribute.COMPLETE.getAttributeCategory().name(), 1L), | entry(CleanCodeAttribute.COMPLETE.getAttributeCategory().name(), 1L), | ||||
entry(CleanCodeAttribute.CONVENTIONAL.getAttributeCategory().name(), 0L) | |||||
); | |||||
entry(CleanCodeAttribute.CONVENTIONAL.getAttributeCategory().name(), 0L)); | |||||
} | } | ||||
@Test | @Test | ||||
public void should_included_selected_non_matching_tag_in_facet() { | public void should_included_selected_non_matching_tag_in_facet() { | ||||
RuleDto rule = db.rules().insert(setSystemTags("tag1", "tag2", "tag3", "tag4", "tag5", "tag6", "tag7", "tag8", "tag9", "tagA")); | RuleDto rule = db.rules().insert(setSystemTags("tag1", "tag2", "tag3", "tag4", "tag5", "tag6", "tag7", "tag8", "tag9", "tagA")); |
private final UuidFactory uuidFactory = UuidFactoryFast.getInstance(); | private final UuidFactory uuidFactory = UuidFactoryFast.getInstance(); | ||||
private final MacroInterpreter macroInterpreter = mock(MacroInterpreter.class); | private final MacroInterpreter macroInterpreter = mock(MacroInterpreter.class); | ||||
private final Languages languages = new Languages(newLanguage("xoo", "Xoo")); | private final Languages languages = new Languages(newLanguage("xoo", "Xoo")); | ||||
private final RuleWsSupport ruleWsSupport = new RuleWsSupport(db.getDbClient(), userSession); | |||||
private final RuleMapper ruleMapper = new RuleMapper(languages, macroInterpreter, new RuleDescriptionFormatter()); | |||||
private final WsActionTester ws = new WsActionTester( | private final WsActionTester ws = new WsActionTester( | ||||
new ShowAction(db.getDbClient(), new RuleMapper(languages, macroInterpreter, new RuleDescriptionFormatter()), | |||||
new ActiveRuleCompleter(db.getDbClient(), languages), | |||||
new RuleWsSupport(db.getDbClient(), userSession))); | |||||
new ShowAction(db.getDbClient(), new RulesResponseFormatter(db.getDbClient(), ruleWsSupport, ruleMapper, languages))); | |||||
private UserDto userDto; | private UserDto userDto; | ||||
@Before | @Before | ||||
.containsExactly(rule.getTags().toArray(new String[0])); | .containsExactly(rule.getTags().toArray(new String[0])); | ||||
} | } | ||||
//<test case name>_when<conditionInCamelCase>_should<assertionInCamelCase> | |||||
@Test | @Test | ||||
public void returnRuleCleanCodeFields_whenEndpointIsCalled() { | public void returnRuleCleanCodeFields_whenEndpointIsCalled() { | ||||
RuleDto rule = db.rules().insert(setTags("tag1", "tag2"), r -> r.setNoteData(null).setNoteUserUuid(null)); | RuleDto rule = db.rules().insert(setTags("tag1", "tag2"), r -> r.setNoteData(null).setNoteUserUuid(null)); |
templateRule.ifPresent(templateRules::add); | templateRule.ifPresent(templateRules::add); | ||||
} | } | ||||
List<RuleParamDto> ruleParameters = dbClient.ruleDao().selectRuleParamsByRuleUuids(dbSession, singletonList(rule.getUuid())); | List<RuleParamDto> ruleParameters = dbClient.ruleDao().selectRuleParamsByRuleUuids(dbSession, singletonList(rule.getUuid())); | ||||
SearchAction.SearchResult searchResult = new SearchAction.SearchResult() | |||||
RulesResponseFormatter.SearchResult searchResult = new RulesResponseFormatter.SearchResult() | |||||
.setRuleParameters(ruleParameters) | .setRuleParameters(ruleParameters) | ||||
.setTemplateRules(templateRules) | .setTemplateRules(templateRules) | ||||
.setTotal(1L); | .setTotal(1L); |
*/ | */ | ||||
package org.sonar.server.rule.ws; | package org.sonar.server.rule.ws; | ||||
import com.google.common.collect.Maps; | |||||
import java.util.Date; | |||||
import java.util.LinkedList; | |||||
import java.util.List; | |||||
import java.util.Map; | |||||
import java.util.Objects; | |||||
import java.util.Set; | import java.util.Set; | ||||
import java.util.stream.Collectors; | |||||
import javax.annotation.Nullable; | |||||
import org.sonar.api.server.ws.Change; | |||||
import org.sonar.api.server.ws.Request; | import org.sonar.api.server.ws.Request; | ||||
import org.sonar.api.server.ws.Response; | import org.sonar.api.server.ws.Response; | ||||
import org.sonar.api.server.ws.WebService; | import org.sonar.api.server.ws.WebService; | ||||
import org.sonar.api.server.ws.WebService.NewAction; | |||||
import org.sonar.db.DbClient; | import org.sonar.db.DbClient; | ||||
import org.sonar.db.DbSession; | import org.sonar.db.DbSession; | ||||
import org.sonar.db.Pagination; | |||||
import org.sonar.db.qualityprofile.QProfileDto; | |||||
import org.sonar.db.rule.RuleDto; | import org.sonar.db.rule.RuleDto; | ||||
import org.sonarqube.ws.MediaTypes; | |||||
import org.sonar.db.rule.RuleListQuery; | |||||
import org.sonar.db.rule.RuleListResult; | |||||
import org.sonar.db.rule.RuleParamDto; | |||||
import org.sonar.server.rule.ws.RulesResponseFormatter.SearchResult; | |||||
import org.sonarqube.ws.Common; | |||||
import org.sonarqube.ws.Rules; | |||||
import org.sonarqube.ws.Rules.ListResponse; | import org.sonarqube.ws.Rules.ListResponse; | ||||
import static com.google.common.base.Strings.nullToEmpty; | |||||
import static java.util.stream.Collectors.toSet; | |||||
import static org.sonar.api.server.ws.WebService.Param.PAGE; | |||||
import static org.sonar.api.server.ws.WebService.Param.PAGE_SIZE; | |||||
import static org.sonar.db.rule.RuleListQuery.RuleListQueryBuilder.newRuleListQueryBuilder; | |||||
import static org.sonar.server.exceptions.NotFoundException.checkFound; | |||||
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_AVAILABLE_SINCE; | |||||
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_QPROFILE; | |||||
import static org.sonar.server.ws.WsUtils.writeProtobuf; | |||||
public class ListAction implements RulesWsAction { | public class ListAction implements RulesWsAction { | ||||
private final DbClient dbClient; | private final DbClient dbClient; | ||||
private final RulesResponseFormatter rulesResponseFormatter; | |||||
public ListAction(DbClient dbClient) { | |||||
public ListAction(DbClient dbClient, RulesResponseFormatter rulesResponseFormatter) { | |||||
this.dbClient = dbClient; | this.dbClient = dbClient; | ||||
this.rulesResponseFormatter = rulesResponseFormatter; | |||||
} | } | ||||
@Override | @Override | ||||
public void define(WebService.NewController controller) { | public void define(WebService.NewController controller) { | ||||
controller | |||||
NewAction action = controller | |||||
.createAction("list") | .createAction("list") | ||||
.setDescription("List rules, excluding the manual rules and the rules with status REMOVED. JSON format is not supported for response.") | |||||
.setDescription("List rules, excluding the external rules and the rules with status REMOVED. " + | |||||
"If a quality profile is provided, it returns only the rules activated in this quality profile. " + | |||||
"If no quality profile is provided, it returns all the rules available to the user.") | |||||
.setSince("5.2") | .setSince("5.2") | ||||
.setInternal(true) | .setInternal(true) | ||||
.setResponseExample(getClass().getResource("list-example.txt")) | .setResponseExample(getClass().getResource("list-example.txt")) | ||||
.setHandler(this); | .setHandler(this); | ||||
action.setChangelog( | |||||
new Change("10.4", "'repository' field changed to 'repo'"), | |||||
new Change("10.4", "Extend the response with 'actives', 'qProfiles' fields"), | |||||
new Change("10.4", "Add pagination"), | |||||
new Change("10.4", String.format("Add the '%s' parameter", PARAM_AVAILABLE_SINCE)), | |||||
new Change("10.4", String.format("Add the '%s' parameter", PARAM_QPROFILE)), | |||||
new Change("10.4", "Add the 'createdAt' sorting parameter")); | |||||
action.createParam(PARAM_AVAILABLE_SINCE) | |||||
.setDescription("Filter rules available since the given date. If no value is provided, all rules are returned. Format is yyyy-MM-dd.") | |||||
.setExampleValue("2014-06-22") | |||||
.setSince("10.4"); | |||||
action.createParam(PARAM_QPROFILE) | |||||
.setDescription("Filter rules that are activated in the given quality profile.") | |||||
.setSince("10.4"); | |||||
action.createSortParams(Set.of("createdAt"), "createdAt", false) | |||||
.setSince("10.4"); | |||||
action.addPagingParamsSince(100, 500, "10.4"); | |||||
} | } | ||||
@Override | @Override | ||||
public void handle(Request wsRequest, Response wsResponse) throws Exception { | |||||
final ListResponse.Builder listResponseBuilder = ListResponse.newBuilder(); | |||||
final ListResponse.Rule.Builder ruleBuilder = ListResponse.Rule.newBuilder(); | |||||
public void handle(Request request, Response response) throws Exception { | |||||
try (DbSession dbSession = dbClient.openSession(false)) { | try (DbSession dbSession = dbClient.openSession(false)) { | ||||
Set<ListResponse.Rule> rules = dbClient.ruleDao().selectEnabled(dbSession).stream() | |||||
.map(dto -> toRule(ruleBuilder, dto)) | |||||
.collect(toSet()); | |||||
listResponseBuilder.addAllRules(rules); | |||||
WsRequest wsRequest = toWsRequest(dbSession, request); | |||||
SearchResult searchResult = doSearch(dbSession, wsRequest); | |||||
ListResponse listResponse = buildResponse(wsRequest, dbSession, searchResult); | |||||
writeProtobuf(listResponse, request, response); | |||||
} | |||||
} | |||||
private WsRequest toWsRequest(DbSession dbSession, Request request) { | |||||
WsRequest wsRequest = new WsRequest(); | |||||
String sortParam = request.param(WebService.Param.SORT); | |||||
if (sortParam != null) { | |||||
wsRequest.setSortField(sortParam) | |||||
.setAscendingSort(request.mandatoryParamAsBoolean(WebService.Param.ASCENDING)); | |||||
} | } | ||||
// JSON response is voluntarily not supported. This WS is for internal use. | |||||
wsResponse.stream().setMediaType(MediaTypes.PROTOBUF); | |||||
listResponseBuilder.build().writeTo(wsResponse.stream().output()); | |||||
return wsRequest | |||||
.setQProfile(getQProfile(dbSession, request)) | |||||
.setPage(request.mandatoryParamAsInt(PAGE)) | |||||
.setPageSize(request.mandatoryParamAsInt(PAGE_SIZE)) | |||||
.setAvailableSince(request.paramAsDate(PARAM_AVAILABLE_SINCE)); | |||||
} | |||||
@Nullable | |||||
private QProfileDto getQProfile(DbSession dbSession, Request request) { | |||||
String profileUuid = request.param(PARAM_QPROFILE); | |||||
if (profileUuid == null) { | |||||
return null; | |||||
} | |||||
QProfileDto foundProfile = dbClient.qualityProfileDao().selectByUuid(dbSession, profileUuid); | |||||
return checkFound(foundProfile, "The specified qualityProfile '%s' does not exist", profileUuid); | |||||
} | |||||
private SearchResult doSearch(DbSession dbSession, WsRequest wsRequest) { | |||||
RuleListResult ruleListResult = dbClient.ruleDao().selectRules(dbSession, | |||||
buildRuleListQuery(wsRequest), | |||||
Pagination.forPage(wsRequest.page).andSize(wsRequest.pageSize)); | |||||
Map<String, RuleDto> rulesByUuid = Maps.uniqueIndex(dbClient.ruleDao().selectByUuids(dbSession, ruleListResult.getUuids()), RuleDto::getUuid); | |||||
Set<String> ruleUuids = rulesByUuid.keySet(); | |||||
List<RuleDto> rules = new LinkedList<>(rulesByUuid.values()); | |||||
List<String> templateRuleUuids = rules.stream() | |||||
.map(RuleDto::getTemplateUuid) | |||||
.filter(Objects::nonNull) | |||||
.toList(); | |||||
List<RuleDto> templateRules = dbClient.ruleDao().selectByUuids(dbSession, templateRuleUuids); | |||||
List<RuleParamDto> ruleParamDtos = dbClient.ruleDao().selectRuleParamsByRuleUuids(dbSession, ruleUuids); | |||||
return new SearchResult() | |||||
.setRules(rules) | |||||
.setRuleParameters(ruleParamDtos) | |||||
.setTemplateRules(templateRules) | |||||
.setTotal(ruleListResult.getTotal()); | |||||
} | } | ||||
private static ListResponse.Rule toRule(ListResponse.Rule.Builder ruleBuilder, RuleDto dto) { | |||||
return ruleBuilder | |||||
.clear() | |||||
.setRepository(dto.getRepositoryKey()) | |||||
.setKey(dto.getRuleKey()) | |||||
.setName(nullToEmpty(dto.getName())) | |||||
.setInternalKey(nullToEmpty(dto.getConfigKey())) | |||||
private static RuleListQuery buildRuleListQuery(WsRequest wsRequest) { | |||||
return newRuleListQueryBuilder() | |||||
.profileUuid(wsRequest.qProfile != null ? wsRequest.qProfile.getRulesProfileUuid() : null) | |||||
.createdAt(wsRequest.availableSince != null ? wsRequest.availableSince.getTime() : null) | |||||
.sortField(wsRequest.sortField) | |||||
.sortDirection(wsRequest.ascendingSort ? "asc" : "desc") | |||||
.build(); | .build(); | ||||
} | } | ||||
private ListResponse buildResponse(WsRequest wsRequest, DbSession dbSession, SearchResult searchResult) { | |||||
QProfileDto qProfile = wsRequest.qProfile; | |||||
Rules.Actives actives = rulesResponseFormatter.formatActiveRules(dbSession, qProfile, searchResult.getRules()); | |||||
Set<String> qProfiles = actives.getActivesMap().values() | |||||
.stream() | |||||
.map(Rules.ActiveList::getActiveListList) | |||||
.flatMap(List::stream) | |||||
.map(Rules.Active::getQProfile) | |||||
.collect(Collectors.toSet()); | |||||
return ListResponse.newBuilder() | |||||
.addAllRules(rulesResponseFormatter.formatRulesList(dbSession, searchResult)) | |||||
.setActives(actives) | |||||
.setQProfiles(rulesResponseFormatter.formatQualityProfiles(dbSession, qProfiles)) | |||||
.setPaging(Common.Paging.newBuilder() | |||||
.setPageIndex(wsRequest.page) | |||||
.setPageSize(searchResult.getRules().size()) | |||||
.setTotal(searchResult.getTotal().intValue()) | |||||
.build()) | |||||
.build(); | |||||
} | |||||
private static class WsRequest { | |||||
private String sortField = null; | |||||
private boolean ascendingSort = true; | |||||
private QProfileDto qProfile = null; | |||||
private int page = 1; | |||||
private int pageSize = 100; | |||||
private Date availableSince = null; | |||||
public WsRequest setSortField(String sortField) { | |||||
this.sortField = sortField; | |||||
return this; | |||||
} | |||||
public WsRequest setAscendingSort(boolean ascendingSort) { | |||||
this.ascendingSort = ascendingSort; | |||||
return this; | |||||
} | |||||
public WsRequest setQProfile(@Nullable QProfileDto qProfile) { | |||||
this.qProfile = qProfile; | |||||
return this; | |||||
} | |||||
public WsRequest setPage(int page) { | |||||
this.page = page; | |||||
return this; | |||||
} | |||||
public WsRequest setPageSize(int pageSize) { | |||||
this.pageSize = pageSize; | |||||
return this; | |||||
} | |||||
public WsRequest setAvailableSince(@Nullable Date availableSince) { | |||||
this.availableSince = availableSince; | |||||
return this; | |||||
} | |||||
} | |||||
} | } |
import org.sonar.db.user.UserDto; | import org.sonar.db.user.UserDto; | ||||
import org.sonar.markdown.Markdown; | import org.sonar.markdown.Markdown; | ||||
import org.sonar.server.rule.RuleDescriptionFormatter; | import org.sonar.server.rule.RuleDescriptionFormatter; | ||||
import org.sonar.server.rule.ws.SearchAction.SearchResult; | |||||
import org.sonar.server.rule.ws.RulesResponseFormatter.SearchResult; | |||||
import org.sonar.server.text.MacroInterpreter; | import org.sonar.server.text.MacroInterpreter; | ||||
import org.sonarqube.ws.Common; | import org.sonarqube.ws.Common; | ||||
import org.sonarqube.ws.Common.RuleScope; | import org.sonarqube.ws.Common.RuleScope; | ||||
private static void setGapDescription(Rules.Rule.Builder ruleResponse, RuleDto ruleDto, Set<String> fieldsToReturn) { | private static void setGapDescription(Rules.Rule.Builder ruleResponse, RuleDto ruleDto, Set<String> fieldsToReturn) { | ||||
String gapDescription = ruleDto.getGapDescription(); | String gapDescription = ruleDto.getGapDescription(); | ||||
if (shouldReturnField(fieldsToReturn, FIELD_GAP_DESCRIPTION) | |||||
&& gapDescription != null) { | |||||
if (shouldReturnField(fieldsToReturn, FIELD_GAP_DESCRIPTION) && gapDescription != null) { | |||||
ruleResponse.setGapDescription(gapDescription); | ruleResponse.setGapDescription(gapDescription); | ||||
} | } | ||||
} | } |
import com.google.common.collect.Multimap; | import com.google.common.collect.Multimap; | ||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import java.util.Collection; | import java.util.Collection; | ||||
import java.util.Collections; | |||||
import java.util.HashMap; | import java.util.HashMap; | ||||
import java.util.HashSet; | |||||
import java.util.List; | import java.util.List; | ||||
import java.util.Map; | import java.util.Map; | ||||
import java.util.Set; | import java.util.Set; | ||||
import java.util.function.Function; | import java.util.function.Function; | ||||
import java.util.stream.Collectors; | import java.util.stream.Collectors; | ||||
import javax.annotation.CheckForNull; | |||||
import javax.annotation.Nullable; | |||||
import org.apache.commons.lang.StringUtils; | import org.apache.commons.lang.StringUtils; | ||||
import org.sonar.api.resources.Language; | import org.sonar.api.resources.Language; | ||||
import org.sonar.api.resources.Languages; | import org.sonar.api.resources.Languages; | ||||
import org.sonar.db.qualityprofile.ActiveRuleParamDto; | import org.sonar.db.qualityprofile.ActiveRuleParamDto; | ||||
import org.sonar.db.qualityprofile.OrgActiveRuleDto; | import org.sonar.db.qualityprofile.OrgActiveRuleDto; | ||||
import org.sonar.db.qualityprofile.QProfileDto; | import org.sonar.db.qualityprofile.QProfileDto; | ||||
import org.sonar.db.rule.DeprecatedRuleKeyDto; | |||||
import org.sonar.db.rule.RuleDto; | import org.sonar.db.rule.RuleDto; | ||||
import org.sonar.db.rule.RuleParamDto; | |||||
import org.sonar.db.user.UserDto; | |||||
import org.sonar.server.es.Facets; | |||||
import org.sonar.server.qualityprofile.ActiveRuleInheritance; | import org.sonar.server.qualityprofile.ActiveRuleInheritance; | ||||
import org.sonar.server.rule.index.RuleQuery; | |||||
import org.sonarqube.ws.Rules; | import org.sonarqube.ws.Rules; | ||||
import org.sonarqube.ws.Rules.SearchResponse; | |||||
import static com.google.common.base.Strings.nullToEmpty; | import static com.google.common.base.Strings.nullToEmpty; | ||||
import static java.util.Collections.emptyMap; | |||||
import static java.util.Collections.singletonList; | import static java.util.Collections.singletonList; | ||||
import static java.util.Optional.ofNullable; | import static java.util.Optional.ofNullable; | ||||
import static org.sonar.server.rule.ws.RulesWsParameters.FIELD_DEPRECATED_KEYS; | |||||
/** | |||||
* Add details about active rules to api/rules/search and api/rules/show | |||||
* web services. | |||||
*/ | |||||
@ServerSide | @ServerSide | ||||
public class ActiveRuleCompleter { | |||||
public class RulesResponseFormatter { | |||||
private final DbClient dbClient; | private final DbClient dbClient; | ||||
private final RuleWsSupport ruleWsSupport; | |||||
private final RuleMapper mapper; | |||||
private final Languages languages; | private final Languages languages; | ||||
public ActiveRuleCompleter(DbClient dbClient, Languages languages) { | |||||
public RulesResponseFormatter(DbClient dbClient, RuleWsSupport ruleWsSupport, RuleMapper mapper, Languages languages) { | |||||
this.dbClient = dbClient; | this.dbClient = dbClient; | ||||
this.ruleWsSupport = ruleWsSupport; | |||||
this.mapper = mapper; | |||||
this.languages = languages; | this.languages = languages; | ||||
} | } | ||||
void completeSearch(DbSession dbSession, RuleQuery query, List<RuleDto> rules, SearchResponse.Builder searchResponse) { | |||||
Set<String> profileUuids = writeActiveRules(dbSession, searchResponse, query, rules); | |||||
searchResponse.setQProfiles(buildQProfiles(dbSession, profileUuids)); | |||||
public List<Rules.Rule> formatRulesSearch(DbSession dbSession, SearchResult result, Set<String> fields) { | |||||
List<RuleDto> rules = result.getRules(); | |||||
Map<String, UserDto> usersByUuid = ruleWsSupport.getUsersByUuid(dbSession, rules); | |||||
Map<String, List<DeprecatedRuleKeyDto>> deprecatedRuleKeysByRuleUuid = getDeprecatedRuleKeysByRuleUuid(dbSession, rules, fields); | |||||
return rules.stream() | |||||
.map(rule -> mapper.toWsRule(rule, result, fields, usersByUuid, deprecatedRuleKeysByRuleUuid)) | |||||
.toList(); | |||||
} | |||||
public List<Rules.Rule> formatRulesList(DbSession dbSession, SearchResult result) { | |||||
Set<String> fields = Set.of("repo", "name", "severity", "lang", "internalKey", "templateKey", "params", "actives", "createdAt", "updatedAt", "deprecatedKeys", "langName"); | |||||
return formatRulesSearch(dbSession, result, fields); | |||||
} | |||||
private Map<String, List<DeprecatedRuleKeyDto>> getDeprecatedRuleKeysByRuleUuid(DbSession dbSession, List<RuleDto> rules, Set<String> fields) { | |||||
if (!RuleMapper.shouldReturnField(fields, FIELD_DEPRECATED_KEYS)) { | |||||
return Collections.emptyMap(); | |||||
} | |||||
Set<String> ruleUuidsSet = rules.stream() | |||||
.map(RuleDto::getUuid) | |||||
.collect(Collectors.toSet()); | |||||
if (ruleUuidsSet.isEmpty()) { | |||||
return Collections.emptyMap(); | |||||
} else { | |||||
return dbClient.ruleDao().selectDeprecatedRuleKeysByRuleUuids(dbSession, ruleUuidsSet).stream() | |||||
.collect(Collectors.groupingBy(DeprecatedRuleKeyDto::getRuleUuid)); | |||||
} | |||||
} | } | ||||
private Set<String> writeActiveRules(DbSession dbSession, SearchResponse.Builder response, RuleQuery query, List<RuleDto> rules) { | |||||
final Set<String> profileUuids = new HashSet<>(); | |||||
Rules.Actives.Builder activesBuilder = response.getActivesBuilder(); | |||||
public Rules.QProfiles formatQualityProfiles(DbSession dbSession, Set<String> profileUuids) { | |||||
Rules.QProfiles.Builder result = Rules.QProfiles.newBuilder(); | |||||
if (profileUuids.isEmpty()) { | |||||
return result.build(); | |||||
} | |||||
// load profiles | |||||
Map<String, QProfileDto> profilesByUuid = dbClient.qualityProfileDao().selectByUuids(dbSession, new ArrayList<>(profileUuids)) | |||||
.stream() | |||||
.collect(Collectors.toMap(QProfileDto::getKee, Function.identity())); | |||||
// load associated parents | |||||
List<String> parentUuids = profilesByUuid.values().stream() | |||||
.map(QProfileDto::getParentKee) | |||||
.filter(StringUtils::isNotEmpty) | |||||
.filter(uuid -> !profilesByUuid.containsKey(uuid)) | |||||
.toList(); | |||||
if (!parentUuids.isEmpty()) { | |||||
dbClient.qualityProfileDao().selectByUuids(dbSession, parentUuids) | |||||
.forEach(p -> profilesByUuid.put(p.getKee(), p)); | |||||
} | |||||
profilesByUuid.values().forEach(p -> writeProfile(result, p)); | |||||
return result.build(); | |||||
} | |||||
public Rules.Actives formatActiveRules(DbSession dbSession, @Nullable QProfileDto profile, List<RuleDto> rules) { | |||||
Rules.Actives.Builder activesBuilder = Rules.Actives.newBuilder(); | |||||
QProfileDto profile = query.getQProfile(); | |||||
if (profile != null) { | if (profile != null) { | ||||
// Load details of active rules on the selected profile | // Load details of active rules on the selected profile | ||||
List<OrgActiveRuleDto> activeRules = dbClient.activeRuleDao().selectByProfile(dbSession, profile); | List<OrgActiveRuleDto> activeRules = dbClient.activeRuleDao().selectByProfile(dbSession, profile); | ||||
for (RuleDto rule : rules) { | for (RuleDto rule : rules) { | ||||
OrgActiveRuleDto activeRule = activeRuleByRuleKey.get(rule.getKey()); | OrgActiveRuleDto activeRule = activeRuleByRuleKey.get(rule.getKey()); | ||||
if (activeRule != null) { | if (activeRule != null) { | ||||
profileUuids.addAll(writeActiveRules(rule.getKey(), singletonList(activeRule), activeRuleParamsByActiveRuleKey, activesBuilder)); | |||||
writeActiveRules(rule.getKey(), singletonList(activeRule), activeRuleParamsByActiveRuleKey, activesBuilder); | |||||
} | } | ||||
} | } | ||||
} else { | } else { | ||||
Multimap<RuleKey, OrgActiveRuleDto> activeRulesByRuleKey = activeRules.stream() | Multimap<RuleKey, OrgActiveRuleDto> activeRulesByRuleKey = activeRules.stream() | ||||
.collect(MoreCollectors.index(OrgActiveRuleDto::getRuleKey)); | .collect(MoreCollectors.index(OrgActiveRuleDto::getRuleKey)); | ||||
ListMultimap<ActiveRuleKey, ActiveRuleParamDto> activeRuleParamsByActiveRuleKey = loadParams(dbSession, activeRules); | ListMultimap<ActiveRuleKey, ActiveRuleParamDto> activeRuleParamsByActiveRuleKey = loadParams(dbSession, activeRules); | ||||
rules.forEach(rule -> profileUuids.addAll(writeActiveRules(rule.getKey(), activeRulesByRuleKey.get(rule.getKey()), activeRuleParamsByActiveRuleKey, activesBuilder))); | |||||
rules.forEach(rule -> writeActiveRules(rule.getKey(), activeRulesByRuleKey.get(rule.getKey()), activeRuleParamsByActiveRuleKey, activesBuilder)); | |||||
} | } | ||||
response.setActives(activesBuilder); | |||||
return profileUuids; | |||||
return activesBuilder.build(); | |||||
} | } | ||||
private static Set<String> writeActiveRules(RuleKey ruleKey, Collection<OrgActiveRuleDto> activeRules, | |||||
private static void writeActiveRules(RuleKey ruleKey, Collection<OrgActiveRuleDto> activeRules, | |||||
ListMultimap<ActiveRuleKey, ActiveRuleParamDto> activeRuleParamsByActiveRuleKey, Rules.Actives.Builder activesBuilder) { | ListMultimap<ActiveRuleKey, ActiveRuleParamDto> activeRuleParamsByActiveRuleKey, Rules.Actives.Builder activesBuilder) { | ||||
final Set<String> profileUuids = new HashSet<>(); | |||||
Rules.ActiveList.Builder activeRulesListResponse = Rules.ActiveList.newBuilder(); | Rules.ActiveList.Builder activeRulesListResponse = Rules.ActiveList.newBuilder(); | ||||
for (OrgActiveRuleDto activeRule : activeRules) { | for (OrgActiveRuleDto activeRule : activeRules) { | ||||
activeRulesListResponse.addActiveList(buildActiveRuleResponse(activeRule, activeRuleParamsByActiveRuleKey.get(activeRule.getKey()))); | activeRulesListResponse.addActiveList(buildActiveRuleResponse(activeRule, activeRuleParamsByActiveRuleKey.get(activeRule.getKey()))); | ||||
profileUuids.add(activeRule.getOrgProfileUuid()); | |||||
} | } | ||||
activesBuilder | activesBuilder | ||||
.getMutableActives() | .getMutableActives() | ||||
.put(ruleKey.toString(), activeRulesListResponse.build()); | .put(ruleKey.toString(), activeRulesListResponse.build()); | ||||
return profileUuids; | |||||
} | } | ||||
private ListMultimap<ActiveRuleKey, ActiveRuleParamDto> loadParams(DbSession dbSession, List<OrgActiveRuleDto> activeRules) { | private ListMultimap<ActiveRuleKey, ActiveRuleParamDto> loadParams(DbSession dbSession, List<OrgActiveRuleDto> activeRules) { | ||||
return activeRuleParamsByActiveRuleKey; | return activeRuleParamsByActiveRuleKey; | ||||
} | } | ||||
List<Rules.Active> completeShow(DbSession dbSession, RuleDto rule) { | |||||
public List<Rules.Active> formatActiveRule(DbSession dbSession, RuleDto rule) { | |||||
List<OrgActiveRuleDto> activeRules = dbClient.activeRuleDao().selectByOrgRuleUuid(dbSession, rule.getUuid()); | List<OrgActiveRuleDto> activeRules = dbClient.activeRuleDao().selectByOrgRuleUuid(dbSession, rule.getUuid()); | ||||
Map<String, ActiveRuleKey> activeRuleUuidsByKey = new HashMap<>(); | Map<String, ActiveRuleKey> activeRuleUuidsByKey = new HashMap<>(); | ||||
for (OrgActiveRuleDto activeRuleDto : activeRules) { | for (OrgActiveRuleDto activeRuleDto : activeRules) { | ||||
return builder.build(); | return builder.build(); | ||||
} | } | ||||
private Rules.QProfiles.Builder buildQProfiles(DbSession dbSession, Set<String> profileUuids) { | |||||
Rules.QProfiles.Builder result = Rules.QProfiles.newBuilder(); | |||||
if (profileUuids.isEmpty()) { | |||||
return result; | |||||
} | |||||
// load profiles | |||||
Map<String, QProfileDto> profilesByUuid = dbClient.qualityProfileDao().selectByUuids(dbSession, new ArrayList<>(profileUuids)) | |||||
.stream() | |||||
.collect(Collectors.toMap(QProfileDto::getKee, Function.identity())); | |||||
// load associated parents | |||||
List<String> parentUuids = profilesByUuid.values().stream() | |||||
.map(QProfileDto::getParentKee) | |||||
.filter(StringUtils::isNotEmpty) | |||||
.filter(uuid -> !profilesByUuid.containsKey(uuid)) | |||||
.toList(); | |||||
if (!parentUuids.isEmpty()) { | |||||
dbClient.qualityProfileDao().selectByUuids(dbSession, parentUuids) | |||||
.forEach(p -> profilesByUuid.put(p.getKee(), p)); | |||||
} | |||||
Map<String, Rules.QProfile> qProfilesMapResponse = result.getMutableQProfiles(); | |||||
profilesByUuid.values().forEach(p -> writeProfile(qProfilesMapResponse, p)); | |||||
return result; | |||||
} | |||||
private void writeProfile(Map<String, Rules.QProfile> profilesResponse, QProfileDto profile) { | |||||
private void writeProfile(Rules.QProfiles.Builder profilesResponse, QProfileDto profile) { | |||||
Rules.QProfile.Builder profileResponse = Rules.QProfile.newBuilder(); | Rules.QProfile.Builder profileResponse = Rules.QProfile.newBuilder(); | ||||
ofNullable(profile.getName()).ifPresent(profileResponse::setName); | ofNullable(profile.getName()).ifPresent(profileResponse::setName); | ||||
} | } | ||||
ofNullable(profile.getParentKee()).ifPresent(profileResponse::setParent); | ofNullable(profile.getParentKee()).ifPresent(profileResponse::setParent); | ||||
profilesResponse.put(profile.getKee(), profileResponse.build()); | |||||
profilesResponse.putQProfiles(profile.getKee(), profileResponse.build()); | |||||
} | |||||
public Rules.Rule formatRule(DbSession dbSession, SearchResult searchResult) { | |||||
RuleDto rule = searchResult.getRules().get(0); | |||||
return mapper.toWsRule(rule, searchResult, Collections.emptySet(), | |||||
ruleWsSupport.getUsersByUuid(dbSession, searchResult.getRules()), emptyMap()); | |||||
} | |||||
static class SearchResult { | |||||
private List<RuleDto> rules; | |||||
private final ListMultimap<String, RuleParamDto> ruleParamsByRuleUuid; | |||||
private final Map<String, RuleDto> templateRulesByRuleUuid; | |||||
private Long total; | |||||
private Facets facets; | |||||
public SearchResult() { | |||||
this.rules = new ArrayList<>(); | |||||
this.ruleParamsByRuleUuid = ArrayListMultimap.create(); | |||||
this.templateRulesByRuleUuid = new HashMap<>(); | |||||
} | |||||
public List<RuleDto> getRules() { | |||||
return rules; | |||||
} | |||||
public SearchResult setRules(List<RuleDto> rules) { | |||||
this.rules = rules; | |||||
return this; | |||||
} | |||||
public ListMultimap<String, RuleParamDto> getRuleParamsByRuleUuid() { | |||||
return ruleParamsByRuleUuid; | |||||
} | |||||
public SearchResult setRuleParameters(List<RuleParamDto> ruleParams) { | |||||
ruleParamsByRuleUuid.clear(); | |||||
for (RuleParamDto ruleParam : ruleParams) { | |||||
ruleParamsByRuleUuid.put(ruleParam.getRuleUuid(), ruleParam); | |||||
} | |||||
return this; | |||||
} | |||||
public Map<String, RuleDto> getTemplateRulesByRuleUuid() { | |||||
return templateRulesByRuleUuid; | |||||
} | |||||
public SearchResult setTemplateRules(List<RuleDto> templateRules) { | |||||
templateRulesByRuleUuid.clear(); | |||||
for (RuleDto templateRule : templateRules) { | |||||
templateRulesByRuleUuid.put(templateRule.getUuid(), templateRule); | |||||
} | |||||
return this; | |||||
} | |||||
public Long getTotal() { | |||||
return total; | |||||
} | |||||
public SearchResult setTotal(Long total) { | |||||
this.total = total; | |||||
return this; | |||||
} | |||||
@CheckForNull | |||||
public Facets getFacets() { | |||||
return facets; | |||||
} | |||||
public SearchResult setFacets(Facets facets) { | |||||
this.facets = facets; | |||||
return this; | |||||
} | |||||
} | } | ||||
} | } |
*/ | */ | ||||
package org.sonar.server.rule.ws; | package org.sonar.server.rule.ws; | ||||
import com.google.common.collect.ArrayListMultimap; | |||||
import com.google.common.collect.ListMultimap; | |||||
import com.google.common.collect.Lists; | import com.google.common.collect.Lists; | ||||
import com.google.common.collect.Maps; | import com.google.common.collect.Maps; | ||||
import com.google.common.collect.Ordering; | import com.google.common.collect.Ordering; | ||||
import java.util.ArrayList; | |||||
import java.util.Arrays; | import java.util.Arrays; | ||||
import java.util.Collection; | import java.util.Collection; | ||||
import java.util.Collections; | |||||
import java.util.HashMap; | import java.util.HashMap; | ||||
import java.util.HashSet; | import java.util.HashSet; | ||||
import java.util.Iterator; | import java.util.Iterator; | ||||
import java.util.LinkedList; | |||||
import java.util.List; | import java.util.List; | ||||
import java.util.Map; | import java.util.Map; | ||||
import java.util.Objects; | import java.util.Objects; | ||||
import java.util.Set; | import java.util.Set; | ||||
import java.util.stream.Collectors; | import java.util.stream.Collectors; | ||||
import javax.annotation.CheckForNull; | |||||
import javax.annotation.Nullable; | import javax.annotation.Nullable; | ||||
import org.sonar.api.issue.impact.SoftwareQuality; | import org.sonar.api.issue.impact.SoftwareQuality; | ||||
import org.sonar.api.rule.Severity; | import org.sonar.api.rule.Severity; | ||||
import org.sonar.api.server.ws.WebService; | import org.sonar.api.server.ws.WebService; | ||||
import org.sonar.db.DbClient; | import org.sonar.db.DbClient; | ||||
import org.sonar.db.DbSession; | import org.sonar.db.DbSession; | ||||
import org.sonar.db.rule.DeprecatedRuleKeyDto; | |||||
import org.sonar.db.rule.RuleDto; | import org.sonar.db.rule.RuleDto; | ||||
import org.sonar.db.rule.RuleParamDto; | import org.sonar.db.rule.RuleParamDto; | ||||
import org.sonar.db.user.UserDto; | |||||
import org.sonar.server.es.Facets; | import org.sonar.server.es.Facets; | ||||
import org.sonar.server.es.SearchIdResult; | import org.sonar.server.es.SearchIdResult; | ||||
import org.sonar.server.es.SearchOptions; | import org.sonar.server.es.SearchOptions; | ||||
import org.sonar.server.rule.index.RuleIndex; | import org.sonar.server.rule.index.RuleIndex; | ||||
import org.sonar.server.rule.index.RuleQuery; | import org.sonar.server.rule.index.RuleQuery; | ||||
import org.sonar.server.rule.ws.RulesResponseFormatter.SearchResult; | |||||
import org.sonarqube.ws.Common; | import org.sonarqube.ws.Common; | ||||
import org.sonarqube.ws.Rules; | |||||
import org.sonarqube.ws.Rules.SearchResponse; | import org.sonarqube.ws.Rules.SearchResponse; | ||||
import static java.lang.String.format; | import static java.lang.String.format; | ||||
import static org.sonar.server.rule.index.RuleIndex.FACET_STATUSES; | import static org.sonar.server.rule.index.RuleIndex.FACET_STATUSES; | ||||
import static org.sonar.server.rule.index.RuleIndex.FACET_TAGS; | import static org.sonar.server.rule.index.RuleIndex.FACET_TAGS; | ||||
import static org.sonar.server.rule.index.RuleIndex.FACET_TYPES; | import static org.sonar.server.rule.index.RuleIndex.FACET_TYPES; | ||||
import static org.sonar.server.rule.ws.RulesWsParameters.FIELD_DEPRECATED_KEYS; | |||||
import static org.sonar.server.rule.ws.RulesWsParameters.OPTIONAL_FIELDS; | import static org.sonar.server.rule.ws.RulesWsParameters.OPTIONAL_FIELDS; | ||||
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_ACTIVE_SEVERITIES; | import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_ACTIVE_SEVERITIES; | ||||
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_CLEAN_CODE_ATTRIBUTE_CATEGORIES; | import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_CLEAN_CODE_ATTRIBUTE_CATEGORIES; | ||||
public static final String ACTION = "search"; | public static final String ACTION = "search"; | ||||
private static final Collection<String> DEFAULT_FACETS = Set.of(PARAM_LANGUAGES, PARAM_REPOSITORIES, "tags"); | private static final Collection<String> DEFAULT_FACETS = Set.of(PARAM_LANGUAGES, PARAM_REPOSITORIES, "tags"); | ||||
private static final String[] POSSIBLE_FACETS = new String[]{ | |||||
private static final String[] POSSIBLE_FACETS = new String[] { | |||||
FACET_LANGUAGES, | FACET_LANGUAGES, | ||||
FACET_REPOSITORIES, | FACET_REPOSITORIES, | ||||
FACET_TAGS, | FACET_TAGS, | ||||
private final RuleQueryFactory ruleQueryFactory; | private final RuleQueryFactory ruleQueryFactory; | ||||
private final DbClient dbClient; | private final DbClient dbClient; | ||||
private final RuleIndex ruleIndex; | private final RuleIndex ruleIndex; | ||||
private final ActiveRuleCompleter activeRuleCompleter; | |||||
private final RuleMapper mapper; | |||||
private final RuleWsSupport ruleWsSupport; | |||||
private final RulesResponseFormatter rulesResponseFormatter; | |||||
public SearchAction(RuleIndex ruleIndex, ActiveRuleCompleter activeRuleCompleter, RuleQueryFactory ruleQueryFactory, DbClient dbClient, RuleMapper mapper, | |||||
RuleWsSupport ruleWsSupport) { | |||||
public SearchAction(RuleIndex ruleIndex, RuleQueryFactory ruleQueryFactory, DbClient dbClient, RulesResponseFormatter rulesResponseFormatter) { | |||||
this.ruleIndex = ruleIndex; | this.ruleIndex = ruleIndex; | ||||
this.activeRuleCompleter = activeRuleCompleter; | |||||
this.ruleQueryFactory = ruleQueryFactory; | this.ruleQueryFactory = ruleQueryFactory; | ||||
this.dbClient = dbClient; | this.dbClient = dbClient; | ||||
this.mapper = mapper; | |||||
this.ruleWsSupport = ruleWsSupport; | |||||
this.rulesResponseFormatter = rulesResponseFormatter; | |||||
} | } | ||||
@Override | @Override | ||||
} | } | ||||
private static void writeStatistics(SearchResponse.Builder response, SearchResult searchResult, SearchOptions context) { | private static void writeStatistics(SearchResponse.Builder response, SearchResult searchResult, SearchOptions context) { | ||||
response.setTotal(searchResult.total); | |||||
response.setTotal(searchResult.getTotal()); | |||||
response.setP(context.getPage()); | response.setP(context.getPage()); | ||||
response.setPs(context.getLimit()); | response.setPs(context.getLimit()); | ||||
response.setPaging(formatPaging(searchResult.total, context.getPage(), context.getLimit())); | |||||
response.setPaging(formatPaging(searchResult.getTotal(), context.getPage(), context.getLimit())); | |||||
} | } | ||||
private static Common.Paging.Builder formatPaging(Long total, int pageIndex, int limit) { | private static Common.Paging.Builder formatPaging(Long total, int pageIndex, int limit) { | ||||
.setTotal(total.intValue()); | .setTotal(total.intValue()); | ||||
} | } | ||||
private void writeRules(DbSession dbSession, SearchResponse.Builder response, SearchResult result, SearchOptions context) { | |||||
Map<String, UserDto> usersByUuid = ruleWsSupport.getUsersByUuid(dbSession, result.rules); | |||||
Map<String, List<DeprecatedRuleKeyDto>> deprecatedRuleKeysByRuleUuid = getDeprecatedRuleKeysByRuleUuid(dbSession, result.rules, context); | |||||
result.rules.forEach(rule -> response.addRules(mapper.toWsRule(rule, result, context.getFields(), usersByUuid, | |||||
deprecatedRuleKeysByRuleUuid))); | |||||
} | |||||
private Map<String, List<DeprecatedRuleKeyDto>> getDeprecatedRuleKeysByRuleUuid(DbSession dbSession, List<RuleDto> rules, SearchOptions context) { | |||||
if (!RuleMapper.shouldReturnField(context.getFields(), FIELD_DEPRECATED_KEYS)) { | |||||
return Collections.emptyMap(); | |||||
} | |||||
Set<String> ruleUuidsSet = rules.stream() | |||||
.map(RuleDto::getUuid) | |||||
.collect(Collectors.toSet()); | |||||
if (ruleUuidsSet.isEmpty()) { | |||||
return Collections.emptyMap(); | |||||
} else { | |||||
return dbClient.ruleDao().selectDeprecatedRuleKeysByRuleUuids(dbSession, ruleUuidsSet).stream() | |||||
.collect(Collectors.groupingBy(DeprecatedRuleKeyDto::getRuleUuid)); | |||||
} | |||||
} | |||||
private static SearchOptions buildSearchOptions(SearchRequest request) { | private static SearchOptions buildSearchOptions(SearchRequest request) { | ||||
SearchOptions context = loadCommonContext(request); | SearchOptions context = loadCommonContext(request); | ||||
SearchOptions searchOptions = new SearchOptions() | SearchOptions searchOptions = new SearchOptions() | ||||
List<String> ruleUuids = result.getUuids(); | List<String> ruleUuids = result.getUuids(); | ||||
// rule order is managed by ES, this order by must be kept when fetching rule details | // rule order is managed by ES, this order by must be kept when fetching rule details | ||||
Map<String, RuleDto> rulesByRuleKey = Maps.uniqueIndex(dbClient.ruleDao().selectByUuids(dbSession, ruleUuids), RuleDto::getUuid); | Map<String, RuleDto> rulesByRuleKey = Maps.uniqueIndex(dbClient.ruleDao().selectByUuids(dbSession, ruleUuids), RuleDto::getUuid); | ||||
List<RuleDto> rules = new ArrayList<>(); | |||||
List<RuleDto> rules = new LinkedList<>(); | |||||
for (String ruleUuid : ruleUuids) { | for (String ruleUuid : ruleUuids) { | ||||
RuleDto rule = rulesByRuleKey.get(ruleUuid); | RuleDto rule = rulesByRuleKey.get(ruleUuid); | ||||
if (rule != null) { | if (rule != null) { | ||||
private void doContextResponse(DbSession dbSession, SearchRequest request, SearchResult result, SearchResponse.Builder response, RuleQuery query) { | private void doContextResponse(DbSession dbSession, SearchRequest request, SearchResult result, SearchResponse.Builder response, RuleQuery query) { | ||||
SearchOptions contextForResponse = loadCommonContext(request); | SearchOptions contextForResponse = loadCommonContext(request); | ||||
writeRules(dbSession, response, result, contextForResponse); | |||||
response.addAllRules(rulesResponseFormatter.formatRulesSearch(dbSession, result, contextForResponse.getFields())); | |||||
if (contextForResponse.getFields().contains("actives")) { | if (contextForResponse.getFields().contains("actives")) { | ||||
activeRuleCompleter.completeSearch(dbSession, query, result.rules, response); | |||||
Rules.Actives actives = rulesResponseFormatter.formatActiveRules(dbSession, query.getQProfile(), result.getRules()); | |||||
Set<String> qProfiles = actives.getActivesMap().values() | |||||
.stream() | |||||
.map(Rules.ActiveList::getActiveListList) | |||||
.flatMap(List::stream) | |||||
.map(Rules.Active::getQProfile) | |||||
.collect(Collectors.toSet()); | |||||
Rules.QProfiles profiles = rulesResponseFormatter.formatQualityProfiles(dbSession, qProfiles); | |||||
response.setActives(actives); | |||||
response.setQProfiles(profiles); | |||||
} | } | ||||
} | } | ||||
private static void writeFacets(SearchResponse.Builder response, SearchRequest request, SearchOptions context, SearchResult results) { | private static void writeFacets(SearchResponse.Builder response, SearchRequest request, SearchOptions context, SearchResult results) { | ||||
Facets resultsFacets = results.getFacets(); | |||||
if (resultsFacets == null) { | |||||
return; | |||||
} | |||||
addMandatoryFacetValues(results, FACET_LANGUAGES, request.getLanguages()); | addMandatoryFacetValues(results, FACET_LANGUAGES, request.getLanguages()); | ||||
addMandatoryFacetValues(results, FACET_REPOSITORIES, request.getRepositories()); | addMandatoryFacetValues(results, FACET_REPOSITORIES, request.getRepositories()); | ||||
addMandatoryFacetValues(results, FACET_STATUSES, ALL_STATUSES_EXCEPT_REMOVED); | addMandatoryFacetValues(results, FACET_STATUSES, ALL_STATUSES_EXCEPT_REMOVED); | ||||
for (String facetName : context.getFacets()) { | for (String facetName : context.getFacets()) { | ||||
facet.clear().setProperty(facetName); | facet.clear().setProperty(facetName); | ||||
Map<String, Long> facets = results.facets.get(facetName); | |||||
Map<String, Long> facets = resultsFacets.get(facetName); | |||||
if (facets != null) { | if (facets != null) { | ||||
Set<String> itemsFromFacets = new HashSet<>(); | Set<String> itemsFromFacets = new HashSet<>(); | ||||
for (Map.Entry<String, Long> facetValue : facets.entrySet()) { | for (Map.Entry<String, Long> facetValue : facets.entrySet()) { | ||||
} | } | ||||
private static void addMandatoryFacetValues(SearchResult results, String facetName, @Nullable Collection<String> mandatoryValues) { | private static void addMandatoryFacetValues(SearchResult results, String facetName, @Nullable Collection<String> mandatoryValues) { | ||||
Map<String, Long> facetValues = results.facets.get(facetName); | |||||
Facets facets = results.getFacets(); | |||||
if (facets == null) { | |||||
return; | |||||
} | |||||
Map<String, Long> facetValues = facets.get(facetName); | |||||
if (facetValues != null) { | if (facetValues != null) { | ||||
Collection<String> valuesToAdd = mandatoryValues == null ? Lists.newArrayList() : mandatoryValues; | Collection<String> valuesToAdd = mandatoryValues == null ? Lists.newArrayList() : mandatoryValues; | ||||
for (String item : valuesToAdd) { | for (String item : valuesToAdd) { | ||||
.setSonarsourceSecurity(request.paramAsStrings(PARAM_SONARSOURCE_SECURITY)); | .setSonarsourceSecurity(request.paramAsStrings(PARAM_SONARSOURCE_SECURITY)); | ||||
} | } | ||||
static class SearchResult { | |||||
private List<RuleDto> rules; | |||||
private final ListMultimap<String, RuleParamDto> ruleParamsByRuleUuid; | |||||
private final Map<String, RuleDto> templateRulesByRuleUuid; | |||||
private Long total; | |||||
private Facets facets; | |||||
public SearchResult() { | |||||
this.rules = new ArrayList<>(); | |||||
this.ruleParamsByRuleUuid = ArrayListMultimap.create(); | |||||
this.templateRulesByRuleUuid = new HashMap<>(); | |||||
} | |||||
public List<RuleDto> getRules() { | |||||
return rules; | |||||
} | |||||
public SearchResult setRules(List<RuleDto> rules) { | |||||
this.rules = rules; | |||||
return this; | |||||
} | |||||
public ListMultimap<String, RuleParamDto> getRuleParamsByRuleUuid() { | |||||
return ruleParamsByRuleUuid; | |||||
} | |||||
public SearchResult setRuleParameters(List<RuleParamDto> ruleParams) { | |||||
ruleParamsByRuleUuid.clear(); | |||||
for (RuleParamDto ruleParam : ruleParams) { | |||||
ruleParamsByRuleUuid.put(ruleParam.getRuleUuid(), ruleParam); | |||||
} | |||||
return this; | |||||
} | |||||
public Map<String, RuleDto> getTemplateRulesByRuleUuid() { | |||||
return templateRulesByRuleUuid; | |||||
} | |||||
public SearchResult setTemplateRules(List<RuleDto> templateRules) { | |||||
templateRulesByRuleUuid.clear(); | |||||
for (RuleDto templateRule : templateRules) { | |||||
templateRulesByRuleUuid.put(templateRule.getUuid(), templateRule); | |||||
} | |||||
return this; | |||||
} | |||||
@CheckForNull | |||||
public Long getTotal() { | |||||
return total; | |||||
} | |||||
public SearchResult setTotal(Long total) { | |||||
this.total = total; | |||||
return this; | |||||
} | |||||
@CheckForNull | |||||
public Facets getFacets() { | |||||
return facets; | |||||
} | |||||
public SearchResult setFacets(Facets facets) { | |||||
this.facets = facets; | |||||
return this; | |||||
} | |||||
} | |||||
private static class SearchRequest { | private static class SearchRequest { | ||||
private List<String> activeSeverities; | private List<String> activeSeverities; | ||||
private List<String> impactSoftwareQualities; | private List<String> impactSoftwareQualities; | ||||
private List<String> cleanCodeAttributesCategories; | private List<String> cleanCodeAttributesCategories; | ||||
private SearchRequest setActiveSeverities(List<String> activeSeverities) { | private SearchRequest setActiveSeverities(List<String> activeSeverities) { | ||||
this.activeSeverities = activeSeverities; | this.activeSeverities = activeSeverities; | ||||
return this; | return this; |
import org.sonarqube.ws.Rules.ShowResponse; | import org.sonarqube.ws.Rules.ShowResponse; | ||||
import static java.lang.String.format; | import static java.lang.String.format; | ||||
import static java.util.Collections.emptyMap; | |||||
import static java.util.Collections.singletonList; | import static java.util.Collections.singletonList; | ||||
import static java.util.Optional.ofNullable; | import static java.util.Optional.ofNullable; | ||||
import static org.sonar.server.ws.WsUtils.writeProtobuf; | import static org.sonar.server.ws.WsUtils.writeProtobuf; | ||||
public static final String PARAM_ACTIVES = "actives"; | public static final String PARAM_ACTIVES = "actives"; | ||||
private final DbClient dbClient; | private final DbClient dbClient; | ||||
private final RuleMapper mapper; | |||||
private final ActiveRuleCompleter activeRuleCompleter; | |||||
private final RuleWsSupport ruleWsSupport; | |||||
private final RulesResponseFormatter rulesResponseFormatter; | |||||
public ShowAction(DbClient dbClient, RuleMapper mapper, ActiveRuleCompleter activeRuleCompleter, RuleWsSupport ruleWsSupport) { | |||||
public ShowAction(DbClient dbClient, RulesResponseFormatter rulesResponseFormatter) { | |||||
this.dbClient = dbClient; | this.dbClient = dbClient; | ||||
this.activeRuleCompleter = activeRuleCompleter; | |||||
this.mapper = mapper; | |||||
this.ruleWsSupport = ruleWsSupport; | |||||
this.rulesResponseFormatter = rulesResponseFormatter; | |||||
} | } | ||||
@Override | @Override | ||||
List<RuleParamDto> ruleParameters = dbClient.ruleDao().selectRuleParamsByRuleUuids(dbSession, singletonList(rule.getUuid())); | List<RuleParamDto> ruleParameters = dbClient.ruleDao().selectRuleParamsByRuleUuids(dbSession, singletonList(rule.getUuid())); | ||||
ShowResponse showResponse = buildResponse(dbSession, request, | ShowResponse showResponse = buildResponse(dbSession, request, | ||||
new SearchAction.SearchResult() | |||||
new RulesResponseFormatter.SearchResult() | |||||
.setRules(singletonList(rule)) | .setRules(singletonList(rule)) | ||||
.setTemplateRules(templateRules) | .setTemplateRules(templateRules) | ||||
.setRuleParameters(ruleParameters) | .setRuleParameters(ruleParameters) | ||||
} | } | ||||
} | } | ||||
private ShowResponse buildResponse(DbSession dbSession, Request request, SearchAction.SearchResult searchResult) { | |||||
private ShowResponse buildResponse(DbSession dbSession, Request request, RulesResponseFormatter.SearchResult searchResult) { | |||||
ShowResponse.Builder responseBuilder = ShowResponse.newBuilder(); | ShowResponse.Builder responseBuilder = ShowResponse.newBuilder(); | ||||
RuleDto rule = searchResult.getRules().get(0); | RuleDto rule = searchResult.getRules().get(0); | ||||
responseBuilder.setRule(mapper.toWsRule(rule, searchResult, Collections.emptySet(), | |||||
ruleWsSupport.getUsersByUuid(dbSession, searchResult.getRules()), emptyMap())); | |||||
responseBuilder.setRule(rulesResponseFormatter.formatRule(dbSession, searchResult)); | |||||
if (request.mandatoryParamAsBoolean(PARAM_ACTIVES)) { | if (request.mandatoryParamAsBoolean(PARAM_ACTIVES)) { | ||||
activeRuleCompleter.completeShow(dbSession, rule).forEach(responseBuilder::addActives); | |||||
responseBuilder.addAllActives(rulesResponseFormatter.formatActiveRule(dbSession, rule)); | |||||
} | } | ||||
return responseBuilder.build(); | return responseBuilder.build(); | ||||
} | } |
} | } | ||||
List<RuleParamDto> ruleParameters = dbClient.ruleDao().selectRuleParamsByRuleUuids(dbSession, singletonList(rule.getUuid())); | List<RuleParamDto> ruleParameters = dbClient.ruleDao().selectRuleParamsByRuleUuids(dbSession, singletonList(rule.getUuid())); | ||||
UpdateResponse.Builder responseBuilder = UpdateResponse.newBuilder(); | UpdateResponse.Builder responseBuilder = UpdateResponse.newBuilder(); | ||||
SearchAction.SearchResult searchResult = new SearchAction.SearchResult() | |||||
RulesResponseFormatter.SearchResult searchResult = new RulesResponseFormatter.SearchResult() | |||||
.setRules(singletonList(rule)) | .setRules(singletonList(rule)) | ||||
.setTemplateRules(templateRules) | .setTemplateRules(templateRules) | ||||
.setRuleParameters(ruleParameters) | .setRuleParameters(ruleParameters) |
import org.sonar.server.rule.WebServerRuleFinderImpl; | import org.sonar.server.rule.WebServerRuleFinderImpl; | ||||
import org.sonar.server.rule.index.RuleIndexDefinition; | import org.sonar.server.rule.index.RuleIndexDefinition; | ||||
import org.sonar.server.rule.index.RuleIndexer; | import org.sonar.server.rule.index.RuleIndexer; | ||||
import org.sonar.server.rule.ws.ActiveRuleCompleter; | |||||
import org.sonar.server.rule.ws.RepositoriesAction; | import org.sonar.server.rule.ws.RepositoriesAction; | ||||
import org.sonar.server.rule.ws.RuleMapper; | import org.sonar.server.rule.ws.RuleMapper; | ||||
import org.sonar.server.rule.ws.RuleQueryFactory; | import org.sonar.server.rule.ws.RuleQueryFactory; | ||||
import org.sonar.server.rule.ws.RuleWsSupport; | import org.sonar.server.rule.ws.RuleWsSupport; | ||||
import org.sonar.server.rule.ws.RulesResponseFormatter; | |||||
import org.sonar.server.rule.ws.RulesWs; | import org.sonar.server.rule.ws.RulesWs; | ||||
import org.sonar.server.rule.ws.TagsAction; | import org.sonar.server.rule.ws.TagsAction; | ||||
import org.sonar.server.saml.ws.SamlValidationModule; | import org.sonar.server.saml.ws.SamlValidationModule; | ||||
org.sonar.server.rule.ws.ListAction.class, | org.sonar.server.rule.ws.ListAction.class, | ||||
TagsAction.class, | TagsAction.class, | ||||
RuleMapper.class, | RuleMapper.class, | ||||
ActiveRuleCompleter.class, | |||||
RulesResponseFormatter.class, | |||||
RepositoriesAction.class, | RepositoriesAction.class, | ||||
RuleQueryFactory.class, | RuleQueryFactory.class, | ||||
org.sonar.server.rule.ws.AppAction.class, | org.sonar.server.rule.ws.AppAction.class, |
import org.sonar.scanner.scan.branch.ProjectBranches; | import org.sonar.scanner.scan.branch.ProjectBranches; | ||||
import org.sonarqube.ws.NewCodePeriods; | import org.sonarqube.ws.NewCodePeriods; | ||||
import org.sonarqube.ws.Qualityprofiles.SearchWsResponse.QualityProfile; | import org.sonarqube.ws.Qualityprofiles.SearchWsResponse.QualityProfile; | ||||
import org.sonarqube.ws.Rules.ListResponse.Rule; | |||||
import org.sonarqube.ws.Rules.Rule; | |||||
import static java.util.Collections.emptySet; | import static java.util.Collections.emptySet; | ||||
public ScannerMediumTester addRule(String key, String repoKey, String internalKey, String name) { | public ScannerMediumTester addRule(String key, String repoKey, String internalKey, String name) { | ||||
Rule.Builder builder = Rule.newBuilder(); | Rule.Builder builder = Rule.newBuilder(); | ||||
builder.setKey(key); | builder.setKey(key); | ||||
builder.setRepository(repoKey); | |||||
builder.setRepo(repoKey); | |||||
if (internalKey != null) { | if (internalKey != null) { | ||||
builder.setInternalKey(internalKey); | builder.setInternalKey(internalKey); | ||||
} | } |
import org.sonar.api.impl.utils.ScannerUtils; | import org.sonar.api.impl.utils.ScannerUtils; | ||||
import org.sonar.api.rule.RuleKey; | import org.sonar.api.rule.RuleKey; | ||||
import org.sonar.api.utils.DateUtils; | import org.sonar.api.utils.DateUtils; | ||||
import org.sonar.api.utils.MessageException; | |||||
import org.sonar.scanner.bootstrap.ScannerWsClient; | import org.sonar.scanner.bootstrap.ScannerWsClient; | ||||
import org.sonarqube.ws.Common.Paging; | |||||
import org.sonarqube.ws.Rules; | import org.sonarqube.ws.Rules; | ||||
import org.sonarqube.ws.Rules.Active; | import org.sonarqube.ws.Rules.Active; | ||||
import org.sonarqube.ws.Rules.Active.Param; | import org.sonarqube.ws.Rules.Active.Param; | ||||
import org.sonarqube.ws.Rules.ActiveList; | import org.sonarqube.ws.Rules.ActiveList; | ||||
import org.sonarqube.ws.Rules.ListResponse; | |||||
import org.sonarqube.ws.Rules.Rule; | import org.sonarqube.ws.Rules.Rule; | ||||
import org.sonarqube.ws.Rules.SearchResponse; | |||||
import org.sonarqube.ws.client.GetRequest; | import org.sonarqube.ws.client.GetRequest; | ||||
public class DefaultActiveRulesLoader implements ActiveRulesLoader { | public class DefaultActiveRulesLoader implements ActiveRulesLoader { | ||||
private static final String RULES_SEARCH_URL = "/api/rules/search.protobuf?" + | |||||
"f=repo,name,severity,lang,internalKey,templateKey,params,actives,createdAt,updatedAt,deprecatedKeys&activation=true"; | |||||
private static final String RULES_SEARCH_URL = "/api/rules/list.protobuf?"; | |||||
private final ScannerWsClient wsClient; | private final ScannerWsClient wsClient; | ||||
while (true) { | while (true) { | ||||
GetRequest getRequest = new GetRequest(getUrl(qualityProfileKey, page, pageSize)); | GetRequest getRequest = new GetRequest(getUrl(qualityProfileKey, page, pageSize)); | ||||
SearchResponse response = loadFromStream(wsClient.call(getRequest).contentStream()); | |||||
ListResponse response = loadFromStream(wsClient.call(getRequest).contentStream()); | |||||
List<LoadedActiveRule> pageRules = readPage(response); | List<LoadedActiveRule> pageRules = readPage(response); | ||||
ruleList.addAll(pageRules); | ruleList.addAll(pageRules); | ||||
loaded += response.getPs(); | |||||
if (response.getTotal() <= loaded) { | |||||
Paging paging = response.getPaging(); | |||||
loaded += paging.getPageSize(); | |||||
if (paging.getTotal() <= loaded) { | |||||
break; | break; | ||||
} | } | ||||
page++; | page++; | ||||
private static String getUrl(String qualityProfileKey, int page, int pageSize) { | private static String getUrl(String qualityProfileKey, int page, int pageSize) { | ||||
StringBuilder builder = new StringBuilder(1024); | StringBuilder builder = new StringBuilder(1024); | ||||
builder.append(RULES_SEARCH_URL); | builder.append(RULES_SEARCH_URL); | ||||
builder.append("&qprofile=").append(ScannerUtils.encodeForUrl(qualityProfileKey)); | |||||
builder.append("qprofile=").append(ScannerUtils.encodeForUrl(qualityProfileKey)); | |||||
builder.append("&ps=").append(pageSize); | builder.append("&ps=").append(pageSize); | ||||
builder.append("&p=").append(page); | builder.append("&p=").append(page); | ||||
return builder.toString(); | return builder.toString(); | ||||
} | } | ||||
private static SearchResponse loadFromStream(InputStream is) { | |||||
private static ListResponse loadFromStream(InputStream is) { | |||||
try { | try { | ||||
return SearchResponse.parseFrom(is); | |||||
return ListResponse.parseFrom(is); | |||||
} catch (IOException e) { | } catch (IOException e) { | ||||
throw new IllegalStateException("Failed to load quality profiles", e); | throw new IllegalStateException("Failed to load quality profiles", e); | ||||
} finally { | } finally { | ||||
} | } | ||||
} | } | ||||
private static List<LoadedActiveRule> readPage(SearchResponse response) { | |||||
private static List<LoadedActiveRule> readPage(ListResponse response) { | |||||
List<LoadedActiveRule> loadedRules = new LinkedList<>(); | List<LoadedActiveRule> loadedRules = new LinkedList<>(); | ||||
List<Rule> rulesList = response.getRulesList(); | List<Rule> rulesList = response.getRulesList(); | ||||
for (Rule r : rulesList) { | for (Rule r : rulesList) { | ||||
ActiveList activeList = actives.get(r.getKey()); | ActiveList activeList = actives.get(r.getKey()); | ||||
if (activeList == null) { | |||||
throw MessageException.of("Elasticsearch indices have become inconsistent. Consider re-indexing. " + | |||||
"Check documentation for more information https://docs.sonarsource.com/sonarqube/latest/setup/troubleshooting"); | |||||
} | |||||
Active active = activeList.getActiveList(0); | Active active = activeList.getActiveList(0); | ||||
LoadedActiveRule loadedRule = new LoadedActiveRule(); | LoadedActiveRule loadedRule = new LoadedActiveRule(); |
package org.sonar.scanner.rule; | package org.sonar.scanner.rule; | ||||
import java.util.List; | import java.util.List; | ||||
import org.sonarqube.ws.Rules.ListResponse.Rule; | |||||
import org.sonarqube.ws.Rules; | |||||
public interface RulesLoader { | public interface RulesLoader { | ||||
List<Rule> load(); | |||||
List<Rules.Rule> load(); | |||||
} | } |
*/ | */ | ||||
package org.sonar.scanner.rule; | package org.sonar.scanner.rule; | ||||
import com.google.common.collect.ImmutableSortedMap; | |||||
import java.io.ByteArrayInputStream; | import java.io.ByteArrayInputStream; | ||||
import java.io.InputStream; | import java.io.InputStream; | ||||
import java.util.Collection; | import java.util.Collection; | ||||
import java.util.Map; | |||||
import java.util.stream.IntStream; | import java.util.stream.IntStream; | ||||
import org.junit.Before; | import org.junit.Before; | ||||
import org.junit.Test; | import org.junit.Test; | ||||
import org.sonar.api.batch.rule.LoadedActiveRule; | import org.sonar.api.batch.rule.LoadedActiveRule; | ||||
import org.sonar.api.rule.RuleKey; | import org.sonar.api.rule.RuleKey; | ||||
import org.sonar.api.rule.Severity; | import org.sonar.api.rule.Severity; | ||||
import org.sonar.api.utils.MessageException; | |||||
import org.sonar.scanner.WsTestUtil; | import org.sonar.scanner.WsTestUtil; | ||||
import org.sonar.scanner.bootstrap.DefaultScannerWsClient; | import org.sonar.scanner.bootstrap.DefaultScannerWsClient; | ||||
import org.sonar.scanner.scan.branch.BranchConfiguration; | import org.sonar.scanner.scan.branch.BranchConfiguration; | ||||
import org.sonarqube.ws.Common; | |||||
import org.sonarqube.ws.Rules; | import org.sonarqube.ws.Rules; | ||||
import org.sonarqube.ws.Rules.Active; | import org.sonarqube.ws.Rules.Active; | ||||
import org.sonarqube.ws.Rules.ActiveList; | import org.sonarqube.ws.Rules.ActiveList; | ||||
import org.sonarqube.ws.Rules.Actives; | import org.sonarqube.ws.Rules.Actives; | ||||
import org.sonarqube.ws.Rules.Rule; | import org.sonarqube.ws.Rules.Rule; | ||||
import org.sonarqube.ws.Rules.SearchResponse; | |||||
import org.sonarqube.ws.Rules.SearchResponse.Builder; | |||||
import static org.assertj.core.api.Assertions.assertThat; | import static org.assertj.core.api.Assertions.assertThat; | ||||
import static org.assertj.core.api.Assertions.assertThatThrownBy; | |||||
import static org.mockito.Mockito.mock; | import static org.mockito.Mockito.mock; | ||||
import static org.mockito.Mockito.verifyNoMoreInteractions; | import static org.mockito.Mockito.verifyNoMoreInteractions; | ||||
import static org.mockito.Mockito.when; | import static org.mockito.Mockito.when; | ||||
} | } | ||||
@Test | @Test | ||||
public void feed_real_response_encode_qp() { | |||||
public void load_shouldRequestRulesAndParseResponse() { | |||||
int total = PAGE_SIZE_1 + PAGE_SIZE_2; | int total = PAGE_SIZE_1 + PAGE_SIZE_2; | ||||
WsTestUtil.mockStream(wsClient, urlOfPage(1), responseOfSize(PAGE_SIZE_1, total)); | |||||
WsTestUtil.mockStream(wsClient, urlOfPage(2), responseOfSize(PAGE_SIZE_2, total)); | |||||
WsTestUtil.mockStream(wsClient, urlOfPage(1), responseOfSize(1, PAGE_SIZE_1, total)); | |||||
WsTestUtil.mockStream(wsClient, urlOfPage(2), responseOfSize(2, PAGE_SIZE_2, total)); | |||||
Collection<LoadedActiveRule> activeRules = loader.load("c+-test_c+-values-17445"); | Collection<LoadedActiveRule> activeRules = loader.load("c+-test_c+-values-17445"); | ||||
assertThat(activeRules).hasSize(total); | assertThat(activeRules).hasSize(total); | ||||
verifyNoMoreInteractions(wsClient); | verifyNoMoreInteractions(wsClient); | ||||
} | } | ||||
@Test | |||||
public void exception_thrown_when_elasticsearch_index_inconsistent() { | |||||
WsTestUtil.mockStream(wsClient, urlOfPage(1), prepareCorruptedResponse()); | |||||
assertThatThrownBy(() -> loader.load("c+-test_c+-values-17445")) | |||||
.isInstanceOf(MessageException.class) | |||||
.hasMessage("Elasticsearch indices have become inconsistent. Consider re-indexing. " + | |||||
"Check documentation for more information https://docs.sonarsource.com/sonarqube/latest/setup/troubleshooting"); | |||||
} | |||||
private String urlOfPage(int page) { | private String urlOfPage(int page) { | ||||
return "/api/rules/search.protobuf?f=repo,name,severity,lang,internalKey,templateKey,params,actives,createdAt,updatedAt,deprecatedKeys&activation=true" | |||||
+ ("") + "&qprofile=c%2B-test_c%2B-values-17445&ps=500&p=" + page + ""; | |||||
return "/api/rules/list.protobuf?qprofile=c%2B-test_c%2B-values-17445&ps=500&p=" + page + ""; | |||||
} | } | ||||
/** | /** | ||||
* Generates an imaginary protobuf result. | * Generates an imaginary protobuf result. | ||||
* | * | ||||
* @param pageIndex page index, that the response should contain | |||||
* @param numberOfRules the number of rules, that the response should contain | * @param numberOfRules the number of rules, that the response should contain | ||||
* @param total the number of results on all pages | * @param total the number of results on all pages | ||||
* @return the binary stream | * @return the binary stream | ||||
*/ | */ | ||||
private InputStream responseOfSize(int numberOfRules, int total) { | |||||
Builder rules = SearchResponse.newBuilder(); | |||||
private InputStream responseOfSize(int pageIndex, int numberOfRules, int total) { | |||||
Rules.ListResponse.Builder rules = Rules.ListResponse.newBuilder(); | |||||
Actives.Builder actives = Actives.newBuilder(); | Actives.Builder actives = Actives.newBuilder(); | ||||
IntStream.rangeClosed(1, numberOfRules) | IntStream.rangeClosed(1, numberOfRules) | ||||
activeBuilder.setSeverity(SEVERITY_VALUE); | activeBuilder.setSeverity(SEVERITY_VALUE); | ||||
} | } | ||||
ActiveList activeList = Rules.ActiveList.newBuilder().addActiveList(activeBuilder).build(); | ActiveList activeList = Rules.ActiveList.newBuilder().addActiveList(activeBuilder).build(); | ||||
actives.putAllActives(ImmutableSortedMap.of(key.toString(), activeList)); | |||||
}); | |||||
rules.setActives(actives); | |||||
rules.setPs(numberOfRules); | |||||
rules.setTotal(total); | |||||
return new ByteArrayInputStream(rules.build().toByteArray()); | |||||
} | |||||
private InputStream prepareCorruptedResponse() { | |||||
Builder rules = SearchResponse.newBuilder(); | |||||
Actives.Builder actives = Actives.newBuilder(); | |||||
IntStream.rangeClosed(1, 3) | |||||
.mapToObj(i -> RuleKey.of("java", "S" + i)) | |||||
.forEach(key -> { | |||||
Rule.Builder ruleBuilder = Rule.newBuilder(); | |||||
ruleBuilder.setKey(key.toString()); | |||||
rules.addRules(ruleBuilder); | |||||
actives.putAllActives(Map.of(key.toString(), activeList)); | |||||
}); | }); | ||||
rules.setActives(actives); | rules.setActives(actives); | ||||
rules.setPs(3); | |||||
rules.setTotal(3); | |||||
rules.setPaging(Common.Paging.newBuilder() | |||||
.setTotal(total) | |||||
.setPageIndex(pageIndex) | |||||
.setPageSize(numberOfRules) | |||||
.build()); | |||||
return new ByteArrayInputStream(rules.build().toByteArray()); | return new ByteArrayInputStream(rules.build().toByteArray()); | ||||
} | } | ||||
} | } |
// WS api/rules/list for internal use only | // WS api/rules/list for internal use only | ||||
message ListResponse { | message ListResponse { | ||||
message Rule { | |||||
optional string repository = 1; | |||||
optional string key = 2; | |||||
optional string internal_key = 3; | |||||
optional string name = 4; | |||||
} | |||||
repeated Rule rules = 1; | repeated Rule rules = 1; | ||||
optional Actives actives = 2; | |||||
optional QProfiles qProfiles = 3; | |||||
optional sonarqube.ws.commons.Paging paging = 4; | |||||
} | } | ||||
// WS api/rules/search | // WS api/rules/search |