public List<String> selectAllProjectUuids(DbSession session) {
return mapper(session).selectAllProjectUuids();
}
+
+ public Set<String> selectProjectUuidsAssociatedToDefaultQualityProfileByLanguage(DbSession session, String language) {
+ Set<String> languageFilters = Set.of(language + "=%", "%;" + language + "=%");
+ return mapper(session).selectProjectUuidsAssociatedToDefaultQualityProfileByLanguage(languageFilters);
+ }
}
import java.util.Collection;
import java.util.List;
+import java.util.Set;
import javax.annotation.CheckForNull;
import org.apache.ibatis.annotations.Param;
List<ProjectDto> selectApplicationsByKeys(@Param("kees") Collection<String> kees);
List<String> selectAllProjectUuids();
+
+ Set<String> selectProjectUuidsAssociatedToDefaultQualityProfileByLanguage(@Param("languageFilters") Set<String> languageFilters);
}
return mapper(dbSession).selectDefaultProfile(language);
}
+ @CheckForNull
+ public String selectDefaultProfileUuid(DbSession dbSession, String language) {
+ return mapper(dbSession).selectDefaultProfileUuid(language);
+ }
+
@CheckForNull
public QProfileDto selectAssociatedToProjectAndLanguage(DbSession dbSession, ProjectDto project, String language) {
return mapper(dbSession).selectAssociatedToProjectUuidAndLanguage(project.getUuid(), language);
List<QProfileDto> selectDefaultProfiles(
@Param("languages") Collection<String> languages);
+ @CheckForNull
+ String selectDefaultProfileUuid(@Param("language") String language);
+
@CheckForNull
QProfileDto selectByNameAndLanguage(
@Param("name") String name,
p.kee=#{key,jdbcType=VARCHAR}
</select>
+ <select id="selectProjectUuidsAssociatedToDefaultQualityProfileByLanguage" parameterType="map" resultType="string">
+ select
+ lm.project_uuid
+ from
+ live_measures lm
+ inner join
+ metrics m on m.uuid = lm.metric_uuid
+ where
+ m.name = 'ncloc_language_distribution'
+ and lm.component_uuid = lm.project_uuid
+ and lm.project_uuid not in (select project_uuid from project_qprofiles)
+ and
+ <foreach collection="languageFilters" index="index" item="languageFilter" open="(" separator=" or " close=")">
+ lm.text_value like #{languageFilter, jdbcType=VARCHAR} escape '/'
+ </foreach>
+ </select>
+
<insert id="insert" parameterType="Project">
INSERT INTO projects (
kee,
and rp.language = dp.language
</select>
+ <select id="selectDefaultProfileUuid" parameterType="string" resultType="string">
+ select dp.qprofile_uuid as kee
+ from default_qprofiles dp
+ where dp.language = #{language, jdbcType=VARCHAR}
+ </select>
+
<select id="selectDefaultBuiltInProfilesWithoutActiveRules" parameterType="map" resultType="org.sonar.db.qualityprofile.QProfileDto">
SELECT
<include refid="qProfileColumns"/>
rp.uuid= #{rulesProfileUuid, jdbcType=VARCHAR}
</select>
</mapper>
-
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
+import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import javax.annotation.Nullable;
import org.sonar.db.DbTester;
import org.sonar.db.audit.AuditPersister;
import org.sonar.db.audit.NoOpAuditPersister;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.measure.LiveMeasureDto;
+import org.sonar.db.metric.MetricDto;
+import org.sonar.db.qualityprofile.QProfileDto;
+import static org.apache.commons.lang.math.RandomUtils.nextInt;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.tuple;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
+import static org.sonar.api.measures.CoreMetrics.NCLOC_LANGUAGE_DISTRIBUTION_KEY;
+import static org.sonar.api.measures.Metric.ValueType.STRING;
public class ProjectDaoTest {
verify(auditPersister, times(1)).updateComponent(any(), any());
}
+ @Test
+ public void select_project_uuids_associated_to_default_quality_profile_for_specific_language() {
+ String language = "xoo";
+ Set<ComponentDto> projects = insertProjects(nextInt(10));
+ insertDefaultQualityProfile(language);
+ insertProjectsLiveMeasures(language, projects);
+
+ Set<String> projectUuids = projectDao.selectProjectUuidsAssociatedToDefaultQualityProfileByLanguage(db.getSession(), language);
+
+ assertThat(projectUuids)
+ .containsExactlyInAnyOrderElementsOf(extractComponentUuids(projects));
+ }
+
+ private void insertDefaultQualityProfile(String language) {
+ QProfileDto profile = db.qualityProfiles().insert(qp -> qp.setIsBuiltIn(true).setLanguage(language));
+ db.qualityProfiles().setAsDefault(profile);
+ }
+
+ private static Set<String> extractComponentUuids(Collection<ComponentDto> components) {
+ return components
+ .stream()
+ .map(ComponentDto::uuid)
+ .collect(Collectors.toSet());
+ }
+
+ private Set<ComponentDto> insertProjects(int number) {
+ return IntStream
+ .rangeClosed(0, number)
+ .mapToObj(x -> db.components().insertPrivateProject())
+ .collect(Collectors.toSet());
+ }
+
+ private Consumer<LiveMeasureDto> configureLiveMeasure(String language, MetricDto metric, ComponentDto project) {
+ return liveMeasure -> liveMeasure
+ .setMetricUuid(metric.getUuid())
+ .setComponentUuid(project.uuid())
+ .setProjectUuid(project.uuid())
+ .setData(language + "=" + nextInt(10));
+ }
+
+ private Consumer<ComponentDto> insertLiveMeasure(String language, MetricDto metric) {
+ return project -> db.measures().insertLiveMeasure(project, metric, configureLiveMeasure(language, metric, project));
+ }
+
+ private void insertProjectsLiveMeasures(String language, Set<ComponentDto> projects) {
+ Consumer<MetricDto> configureMetric = metric -> metric
+ .setValueType(STRING.name())
+ .setKey(NCLOC_LANGUAGE_DISTRIBUTION_KEY);
+
+ MetricDto metric = db.measures().insertMetric(configureMetric);
+
+ projects
+ .forEach(insertLiveMeasure(language, metric));
+ }
+
private void assertProject(ProjectDto dto, String name, String kee, String uuid, String desc, @Nullable String tags, boolean isPrivate) {
assertThat(dto).extracting("name", "kee", "key", "uuid", "description", "tagsString", "private")
.containsExactly(name, kee, kee, uuid, desc, tags, isPrivate);
assertThat(underTest.selectDefaultProfile(dbSession, "js")).isNull();
}
+ @Test
+ public void selectDefaultProfileUuid() {
+ createSharedData();
+
+ assertThat(underTest.selectDefaultProfileUuid(dbSession, "java")).isEqualTo("java_sonar_way");
+ assertThat(underTest.selectDefaultProfileUuid(dbSession, "js")).isNull();
+ }
+
@Test
public void selectDefaultProfiles() {
createSharedData();
import com.google.gson.GsonBuilder;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
+import java.util.function.Predicate;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
import javax.annotation.Nullable;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.sonar.server.qualityprofile.ActiveRuleChange;
import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.Collections.emptyList;
import static java.util.function.Predicate.not;
import static org.sonar.server.qualityprofile.ActiveRuleChange.Type.ACTIVATED;
import static org.sonar.server.qualityprofile.ActiveRuleChange.Type.DEACTIVATED;
.map(RuleKey::toString)
.collect(Collectors.toSet());
- Map<String, String> projectKeyAndUuids = getProjectKeyAndUuids(profiles);
-
if (activatedRules.isEmpty() && deactivatedRules.isEmpty()) {
return;
}
- for (Map.Entry<String, String> entry : projectKeyAndUuids.entrySet()) {
+ Map<String, String> projectsUuidByKey = getProjectsUuidByKey(profiles, language);
+
+ for (Map.Entry<String, String> entry : projectsUuidByKey.entrySet()) {
persistPushEvent(entry.getKey(), activatedRules.toArray(new RuleChange[0]), deactivatedRules, language, entry.getValue());
}
}
return Optional.empty();
}
- private Map<String, String> getProjectKeyAndUuids(Collection<QProfileDto> profiles) {
- Map<String, String> projectKeyAndUuids = new HashMap<>();
+ private Map<String, String> getProjectsUuidByKey(Collection<QProfileDto> profiles, String language) {
try (DbSession dbSession = dbClient.openSession(false)) {
- for (QProfileDto profileDto : profiles) {
- List<ProjectQprofileAssociationDto> associationDtos = dbClient.qualityProfileDao().selectSelectedProjects(dbSession, profileDto, null);
- for (ProjectQprofileAssociationDto associationDto : associationDtos) {
- projectKeyAndUuids.put(associationDto.getProjectKey(), associationDto.getProjectUuid());
- }
- }
- return projectKeyAndUuids;
+ Map<Boolean, List<QProfileDto>> profilesByDefaultStatus = classifyQualityProfilesByDefaultStatus(dbSession, profiles, language);
+
+ List<ProjectDto> defaultAssociatedProjects = getDefaultAssociatedQualityProfileProjects(dbSession, profilesByDefaultStatus.get(true), language);
+ List<ProjectDto> manuallyAssociatedProjects = getManuallyAssociatedQualityProfileProjects(dbSession, profilesByDefaultStatus.get(false));
+
+ return Stream
+ .concat(manuallyAssociatedProjects.stream(), defaultAssociatedProjects.stream())
+ .collect(Collectors.toMap(ProjectDto::getKey, ProjectDto::getUuid));
}
}
+ private Map<Boolean, List<QProfileDto>> classifyQualityProfilesByDefaultStatus(DbSession dbSession, Collection<QProfileDto> profiles, String language) {
+ String defaultQualityProfileUuid = dbClient.qualityProfileDao().selectDefaultProfileUuid(dbSession, language);
+ Predicate<QProfileDto> isDefaultQualityProfile = profile -> profile.getKee().equals(defaultQualityProfileUuid);
+
+ return profiles
+ .stream()
+ .collect(Collectors.partitioningBy(isDefaultQualityProfile));
+ }
+
+ private List<ProjectDto> getDefaultAssociatedQualityProfileProjects(DbSession dbSession, List<QProfileDto> defaultProfiles, String language) {
+ if (defaultProfiles.isEmpty()) {
+ return emptyList();
+ }
+
+ return getDefaultQualityProfileAssociatedProjects(dbSession, language);
+ }
+
+ private List<ProjectDto> getDefaultQualityProfileAssociatedProjects(DbSession dbSession, String language) {
+ Set<String> associatedProjectUuids = dbClient.projectDao().selectProjectUuidsAssociatedToDefaultQualityProfileByLanguage(dbSession, language);
+ return dbClient.projectDao().selectByUuids(dbSession, associatedProjectUuids);
+ }
+
+ private List<ProjectDto> getManuallyAssociatedQualityProfileProjects(DbSession dbSession, List<QProfileDto> profiles) {
+ return profiles
+ .stream()
+ .map(profile -> getQualityProfileAssociatedProjects(dbSession, profile))
+ .flatMap(Collection::stream)
+ .collect(Collectors.toList());
+ }
+
+ private List<ProjectDto> getQualityProfileAssociatedProjects(DbSession dbSession, QProfileDto profile) {
+ Set<String> projectUuids = getQualityProfileAssociatedProjectUuids(dbSession, profile);
+ return dbClient.projectDao().selectByUuids(dbSession, projectUuids);
+ }
+
+ private Set<String> getQualityProfileAssociatedProjectUuids(DbSession dbSession, QProfileDto profile) {
+ List<ProjectQprofileAssociationDto> associations = dbClient.qualityProfileDao().selectSelectedProjects(dbSession, profile, null);
+
+ return associations
+ .stream()
+ .map(ProjectQprofileAssociationDto::getProjectUuid)
+ .collect(Collectors.toSet());
+ }
+
+
+
+
private static byte[] serializeIssueToPushEvent(RuleSetChangedEvent event) {
return GSON.toJson(event).getBytes(UTF_8);
}
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
+import java.util.List;
import java.util.Set;
+import java.util.function.Consumer;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.api.rule.RuleKey;
import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.measure.LiveMeasureDto;
+import org.sonar.db.metric.MetricDto;
import org.sonar.db.project.ProjectDto;
import org.sonar.db.pushevent.PushEventDto;
import org.sonar.db.qualityprofile.ActiveRuleDto;
import static java.util.List.of;
import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.apache.commons.lang.math.RandomUtils.nextInt;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.api.measures.CoreMetrics.NCLOC_LANGUAGE_DISTRIBUTION_KEY;
+import static org.sonar.api.measures.Metric.ValueType.STRING;
import static org.sonar.db.rule.RuleTesting.newCustomRule;
import static org.sonar.db.rule.RuleTesting.newTemplateRule;
import static org.sonar.server.qualityprofile.ActiveRuleChange.Type.ACTIVATED;
.setDescriptionFormat(RuleDto.Format.MARKDOWN);
db.rules().insert(rule1);
- ActiveRuleDto activeRuleDto = ActiveRuleDto.createFor(qualityProfileDto, rule1);
-
- ActiveRuleChange activeRuleChange = new ActiveRuleChange(ACTIVATED, activeRuleDto, rule1);
- activeRuleChange.setParameter("paramChangeKey", "paramChangeValue");
+ ActiveRuleChange activeRuleChange = changeActiveRule(qualityProfileDto, rule1, "paramChangeKey", "paramChangeValue");
Collection<QProfileDto> profiles = Collections.singleton(qualityProfileDto);
underTest.distributeRuleChangeEvent(profiles, of(activeRuleChange), "xoo");
- Deque<PushEventDto> events = db.getDbClient().pushEventDao()
- .selectChunkByProjectUuids(db.getSession(), Set.of(project.getUuid()), 1l, null, 1);
+ Deque<PushEventDto> events = getProjectEvents(project.getUuid());
assertThat(events).isNotEmpty().hasSize(1);
assertThat(events.getFirst())
"\"deactivatedRules\":[]");
}
+ @Test
+ public void distributeRuleChangeEvent_when_project_has_only_default_quality_profiles() {
+ String language = "xoo";
+ ComponentDto project = db.components().insertPrivateProject();
+ RuleDto templateRule = insertTemplateRule();
+ QProfileDto defaultQualityProfile = insertDefaultQualityProfile(language);
+ RuleDto rule = insertCustomRule(templateRule, language, "<div>line1\nline2</div>");
+ ActiveRuleChange activeRuleChange = changeActiveRule(defaultQualityProfile, rule, "paramChangeKey", "paramChangeValue");
+ insertQualityProfileLiveMeasure(project, language, NCLOC_LANGUAGE_DISTRIBUTION_KEY);
+
+ db.getSession().commit();
+
+ underTest.distributeRuleChangeEvent(List.of(defaultQualityProfile), of(activeRuleChange), language);
+
+ Deque<PushEventDto> events = getProjectEvents(project.projectUuid());
+
+ assertThat(events)
+ .hasSize(1);
+
+ assertThat(events.getFirst())
+ .extracting(PushEventDto::getName, PushEventDto::getLanguage)
+ .contains("RuleSetChanged", "xoo");
+
+ String ruleSetChangedEvent = new String(events.getFirst().getPayload(), StandardCharsets.UTF_8);
+
+ assertThat(ruleSetChangedEvent)
+ .contains("\"activatedRules\":[{\"key\":\"repo:ruleKey\"," +
+ "\"language\":\"xoo\"," +
+ "\"templateKey\":\"xoo:template-key\"," +
+ "\"params\":[{\"key\":\"paramChangeKey\",\"value\":\"paramChangeValue\"}]}]," +
+ "\"deactivatedRules\":[]");
+ }
+
+ private Deque<PushEventDto> getProjectEvents(String projectUuid) {
+ return db.getDbClient()
+ .pushEventDao()
+ .selectChunkByProjectUuids(db.getSession(), Set.of(projectUuid), 1L, null, 1);
+ }
+
+ private ActiveRuleChange changeActiveRule(QProfileDto defaultQualityProfile, RuleDto rule, String changeKey, String changeValue) {
+ ActiveRuleDto activeRuleDto = ActiveRuleDto.createFor(defaultQualityProfile, rule);
+ ActiveRuleChange activeRuleChange = new ActiveRuleChange(ACTIVATED, activeRuleDto, rule);
+ return activeRuleChange.setParameter(changeKey, changeValue);
+ }
+
+ private RuleDto insertCustomRule(RuleDto templateRule, String language, String description) {
+ RuleDto rule = newCustomRule(templateRule, description)
+ .setLanguage(language)
+ .setRepositoryKey("repo")
+ .setRuleKey("ruleKey")
+ .setDescriptionFormat(RuleDto.Format.MARKDOWN);
+
+ db.rules().insert(rule);
+ return rule;
+ }
+
+ private QProfileDto insertDefaultQualityProfile(String language) {
+ Consumer<QProfileDto> configureQualityProfile = profile -> profile
+ .setIsBuiltIn(true)
+ .setLanguage(language);
+
+ QProfileDto defaultQualityProfile = db.qualityProfiles().insert(configureQualityProfile);
+ db.qualityProfiles().setAsDefault(defaultQualityProfile);
+
+ return defaultQualityProfile;
+ }
+
+ private RuleDto insertTemplateRule() {
+ RuleKey key = RuleKey.of("xoo", "template-key");
+ RuleDto templateRule = newTemplateRule(key);
+ db.rules().insert(templateRule);
+ return templateRule;
+ }
+
@Test
public void publishRuleActivationToSonarLintClients() {
ProjectDto projectDao = new ProjectDto().setUuid("project-uuid");
underTest.publishRuleActivationToSonarLintClients(projectDao, activatedQualityProfile, deactivatedQualityProfile);
- Deque<PushEventDto> events = db.getDbClient().pushEventDao()
- .selectChunkByProjectUuids(db.getSession(), Set.of(projectDao.getUuid()), 1l, null, 1);
+ Deque<PushEventDto> events = getProjectEvents(projectDao.getUuid());
assertThat(events).isNotEmpty().hasSize(1);
assertThat(events.getFirst())
"\"deactivatedRules\":[\"repo2:ruleKey2\"]");
}
+ private void insertQualityProfileLiveMeasure(ComponentDto project, String language, String metricKey) {
+ MetricDto metric = insertMetric(metricKey);
+
+ Consumer<LiveMeasureDto> configureLiveMeasure = liveMeasure -> liveMeasure
+ .setMetricUuid(metric.getUuid())
+ .setComponentUuid(project.uuid())
+ .setProjectUuid(project.uuid())
+ .setData(language + "=" + nextInt(10));
+
+ db.measures().insertLiveMeasure(project, metric, configureLiveMeasure);
+ }
+
+ private MetricDto insertMetric(String metricKey) {
+ Consumer<MetricDto> configureMetric = metric -> metric
+ .setUuid("uuid")
+ .setValueType(STRING.name())
+ .setKey(metricKey);
+
+ return db.measures().insertMetric(configureMetric);
+ }
}