]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-17110 Register events on default quality profile updates
authorKlaudio Sinani <klaudio.sinani@sonarsource.com>
Tue, 4 Oct 2022 06:49:07 +0000 (08:49 +0200)
committersonartech <sonartech@sonarsource.com>
Wed, 12 Oct 2022 20:03:43 +0000 (20:03 +0000)
* SONAR-17110 Exit earlier if `activatedRules` & `activatedRules` are empty
* SONAR-17110 Add unit-tests for `QualityProfileDao#selectDefaultProfileUuid` method + remove logging
* SONAR-17110 Overall improvements
* SONAR-17110 Minor overall improvements

server/sonar-db-dao/src/main/java/org/sonar/db/project/ProjectDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/project/ProjectMapper.java
server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/QualityProfileDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/QualityProfileMapper.java
server/sonar-db-dao/src/main/resources/org/sonar/db/project/ProjectMapper.xml
server/sonar-db-dao/src/main/resources/org/sonar/db/qualityprofile/QualityProfileMapper.xml
server/sonar-db-dao/src/test/java/org/sonar/db/project/ProjectDaoTest.java
server/sonar-db-dao/src/test/java/org/sonar/db/qualityprofile/QualityProfileDaoTest.java
server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/qualityprofile/QualityProfileChangeEventServiceImpl.java
server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/qualityprofile/QualityProfileChangeEventServiceImplTest.java

index ca78366e668c0afe8134d41854030fdb59e9d97b..1d6128551cf81af2b5025453a108c28ca20a1378 100644 (file)
@@ -121,4 +121,9 @@ public class ProjectDao implements Dao {
   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);
+  }
 }
index e2715aafa4f2c7706e4877f50db43c3cdfb6e815..3fc983b8eb44bfd7a6c5fad277b2a993d887ddc3 100644 (file)
@@ -21,6 +21,7 @@ package org.sonar.db.project;
 
 import java.util.Collection;
 import java.util.List;
+import java.util.Set;
 import javax.annotation.CheckForNull;
 import org.apache.ibatis.annotations.Param;
 
@@ -61,4 +62,6 @@ public interface ProjectMapper {
   List<ProjectDto> selectApplicationsByKeys(@Param("kees") Collection<String> kees);
 
   List<String> selectAllProjectUuids();
+
+  Set<String> selectProjectUuidsAssociatedToDefaultQualityProfileByLanguage(@Param("languageFilters") Set<String> languageFilters);
 }
index 7d2d4c1838b9aa82bc36c030873b57f90eb2061b..0b261675573836fca3d647827bdda5b2787e418e 100644 (file)
@@ -154,6 +154,11 @@ public class QualityProfileDao implements Dao {
     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);
index 27fc6f28def8627844788ee5afa6b73754f91be7..ec6a84fd1f2ba4af8714cb6fd5cd1db2c01d795b 100644 (file)
@@ -57,6 +57,9 @@ public interface QualityProfileMapper {
   List<QProfileDto> selectDefaultProfiles(
     @Param("languages") Collection<String> languages);
 
+  @CheckForNull
+  String selectDefaultProfileUuid(@Param("language") String language);
+
   @CheckForNull
   QProfileDto selectByNameAndLanguage(
     @Param("name") String name,
index c8dd6a244a3be0ef446d525a72c3c1a8ad46bfd0..87ee3c5edce65d03e364429cec0d29a7c1593711 100644 (file)
       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,
index fdcd8dc0b5648f490c139abd57e4273ec8a48403..a48cbe39e7e3c35c00076402940612365432687b 100644 (file)
       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>
-
index ea65092c68810240718b983362788327c8882a13..bdf9822bc33b0231bed55fbe08a815dd4f61d711 100644 (file)
@@ -21,11 +21,13 @@ package org.sonar.db.project;
 
 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;
@@ -38,7 +40,12 @@ import org.sonar.api.utils.System2;
 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;
@@ -46,6 +53,8 @@ import static org.mockito.Mockito.mock;
 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 {
 
@@ -257,6 +266,61 @@ 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);
index 4ad2feca8051ef4cc46691371b6109e682acf846..dee8e09339f526a555418f1b8cc073c3f6d8c0ba 100644 (file)
@@ -342,6 +342,14 @@ public class QualityProfileDaoTest {
     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();
index bb4bd7a046152e46da81da2dd1d2c1f8af4220af..99e0c44a6eb6f311998a1e6362c515cd313bc440 100644 (file)
@@ -23,14 +23,15 @@ import com.google.gson.Gson;
 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;
@@ -52,6 +53,7 @@ import org.sonar.db.rule.RuleDto;
 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;
@@ -198,13 +200,13 @@ public class QualityProfileChangeEventServiceImpl implements QualityProfileChang
       .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());
     }
   }
@@ -238,19 +240,66 @@ public class QualityProfileChangeEventServiceImpl implements QualityProfileChang
     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);
   }
index 1cab5c35a699e18b6f3aa8d288bd01875490ca51..3bc395f22c0bdd55f77a8e4c936895837da293bd 100644 (file)
@@ -23,11 +23,16 @@ import java.nio.charset.StandardCharsets;
 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;
@@ -41,7 +46,10 @@ import org.sonarqube.ws.Common;
 
 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;
@@ -67,10 +75,7 @@ public class QualityProfileChangeEventServiceImplTest {
       .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);
 
@@ -79,8 +84,7 @@ public class QualityProfileChangeEventServiceImplTest {
 
     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())
@@ -97,6 +101,80 @@ public class QualityProfileChangeEventServiceImplTest {
         "\"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");
@@ -123,8 +201,7 @@ public class QualityProfileChangeEventServiceImplTest {
 
     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())
@@ -140,4 +217,24 @@ public class QualityProfileChangeEventServiceImplTest {
         "\"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);
+  }
 }