*/
package org.sonar.ce.task.projectanalysis.step;
-import com.google.common.collect.ImmutableList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import org.sonar.db.event.EventDto;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.sonar.ce.task.projectanalysis.component.Component.Type.DIRECTORY;
import static org.sonar.ce.task.projectanalysis.component.Component.Type.PROJECT;
import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder;
import static org.sonar.db.event.EventDto.CATEGORY_ALERT;
+import static org.sonar.db.event.EventDto.CATEGORY_ISSUE_DETECTION;
import static org.sonar.db.event.EventDto.CATEGORY_PROFILE;
import static org.sonar.db.event.EventDto.CATEGORY_VERSION;
@Rule
public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
- private Date someDate = new Date(150000000L);
+ private final Date someDate = new Date(150000000L);
- private EventRepository eventRepository = mock(EventRepository.class);
- private UuidFactory uuidFactory = UuidFactoryImpl.INSTANCE;
+ private final EventRepository eventRepository = mock(EventRepository.class);
+ private final UuidFactory uuidFactory = UuidFactoryImpl.INSTANCE;
private PersistEventsStep underTest;
when(system2.now()).thenReturn(NOW);
treeRootHolder.setRoot(ROOT);
Event alert = Event.createAlert("Failed", null, "Open issues > 0");
- when(eventRepository.getEvents()).thenReturn(ImmutableList.of(alert));
+ when(eventRepository.getEvents()).thenReturn(List.of(alert));
underTest.execute(new TestComputationStepContext());
when(system2.now()).thenReturn(NOW);
treeRootHolder.setRoot(ROOT);
Event profile = Event.createProfile("foo", null, "bar");
- when(eventRepository.getEvents()).thenReturn(ImmutableList.of(profile));
+ when(eventRepository.getEvents()).thenReturn(List.of(profile));
underTest.execute(new TestComputationStepContext());
assertThat(eventDto.getCreatedAt()).isEqualTo(NOW);
}
+ @Test
+ public void execute_whenIssueDetectionEventRaised_shouldPersist() {
+ when(system2.now()).thenReturn(NOW);
+ treeRootHolder.setRoot(ROOT);
+ Event issueDetection = Event.createIssueDetection("Capabilities have changed (Xoo)");
+ when(eventRepository.getEvents()).thenReturn(List.of(issueDetection));
+
+ underTest.execute(new TestComputationStepContext());
+
+ assertThat(dbTester.countRowsOfTable(dbTester.getSession(), "events")).isEqualTo(2);
+ List<EventDto> eventDtos = dbTester.getDbClient().eventDao().selectByComponentUuid(dbTester.getSession(), ROOT.getUuid());
+ assertThat(eventDtos)
+ .extracting(EventDto::getCategory)
+ .containsOnly(CATEGORY_ISSUE_DETECTION, CATEGORY_VERSION);
+ EventDto eventDto = eventDtos.stream()
+ .filter(t -> CATEGORY_ISSUE_DETECTION.equals(t.getCategory()))
+ .findAny()
+ .orElseGet(() -> fail("Issue detection event not found"));
+ assertThat(eventDto.getComponentUuid()).isEqualTo(ROOT.getUuid());
+ assertThat(eventDto.getName()).isEqualTo(issueDetection.getName());
+ assertThat(eventDto.getDescription()).isEqualTo(issueDetection.getDescription());
+ assertThat(eventDto.getCategory()).isEqualTo(CATEGORY_ISSUE_DETECTION);
+ assertThat(eventDto.getData()).isNull();
+ assertThat(eventDto.getDate()).isEqualTo(analysisMetadataHolder.getAnalysisDate());
+ assertThat(eventDto.getCreatedAt()).isEqualTo(NOW);
+ }
+
@Test
public void keep_one_event_by_version() {
ComponentDto projectDto = dbTester.components().insertPublicProject().getMainBranchComponent();
this.description = description;
}
+ private Event(String name, Category category) {
+ this.name = requireNonNull(name);
+ this.category = requireNonNull(category);
+ this.data = null;
+ this.description = null;
+ }
+
public static Event createAlert(String name, @Nullable String data, @Nullable String description) {
return new Event(name, Category.ALERT, data, description);
}
return new Event(name, Category.PROFILE, data, description);
}
+ public static Event createIssueDetection(String name) {
+ return new Event(name, Category.ISSUE_DETECTION);
+ }
+
public String getName() {
return name;
}
}
public enum Category {
- ALERT, PROFILE
+ ALERT, PROFILE, ISSUE_DETECTION
}
}
QualityProfile baseProfile = baseProfiles.get(profile.getQpKey());
if (baseProfile == null) {
register(profile, ADDED);
- } else if (profile.getRulesUpdatedAt().after(baseProfile.getRulesUpdatedAt())) {
+ } else if (profile.getRulesUpdatedAt().after(baseProfile.getRulesUpdatedAt())) {
register(baseProfile, UPDATED);
} else {
register(baseProfile, UNCHANGED);
--- /dev/null
+/*
+ * 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.ce.task.projectanalysis.step;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Optional;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.resources.Language;
+import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder;
+import org.sonar.ce.task.projectanalysis.analysis.ScannerPlugin;
+import org.sonar.ce.task.projectanalysis.component.Component;
+import org.sonar.ce.task.projectanalysis.component.TreeRootHolder;
+import org.sonar.ce.task.projectanalysis.event.Event;
+import org.sonar.ce.task.projectanalysis.event.EventRepository;
+import org.sonar.ce.task.projectanalysis.language.LanguageRepository;
+import org.sonar.ce.task.projectanalysis.measure.Measure;
+import org.sonar.ce.task.projectanalysis.measure.MeasureRepository;
+import org.sonar.ce.task.projectanalysis.metric.Metric;
+import org.sonar.ce.task.projectanalysis.metric.MetricRepository;
+import org.sonar.ce.task.step.ComputationStep;
+import org.sonar.server.qualityprofile.QPMeasureData;
+import org.sonar.server.qualityprofile.QualityProfile;
+
+import static java.lang.String.format;
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Computation of Issue Detection events
+ * As it depends upon {@link CoreMetrics#QUALITY_PROFILES_KEY}, it must be executed after {@link ComputeQProfileMeasureStep}
+ */
+public class IssueDetectionEventsStep implements ComputationStep {
+ private final TreeRootHolder treeRootHolder;
+ private final MetricRepository metricRepository;
+ private final MeasureRepository measureRepository;
+ private final EventRepository eventRepository;
+ private final LanguageRepository languageRepository;
+ private final AnalysisMetadataHolder analysisMetadataHolder;
+
+ public IssueDetectionEventsStep(TreeRootHolder treeRootHolder, MetricRepository metricRepository,
+ MeasureRepository measureRepository, LanguageRepository languageRepository, EventRepository eventRepository,
+ AnalysisMetadataHolder analysisMetadataHolder) {
+ this.treeRootHolder = treeRootHolder;
+ this.metricRepository = metricRepository;
+ this.measureRepository = measureRepository;
+ this.eventRepository = eventRepository;
+ this.languageRepository = languageRepository;
+ this.analysisMetadataHolder = analysisMetadataHolder;
+ }
+
+ @Override
+ public void execute(Context context) {
+ executeForBranch(treeRootHolder.getRoot());
+ }
+
+ private void executeForBranch(Component branchComponent) {
+ Metric qualityProfileMetric = metricRepository.getByKey(CoreMetrics.QUALITY_PROFILES_KEY);
+ Optional<Measure> baseMeasure = measureRepository.getBaseMeasure(branchComponent, qualityProfileMetric);
+ if (baseMeasure.isEmpty()) {
+ // first analysis -> do not generate events
+ return;
+ }
+
+ // Load profiles used in current analysis for which at least one file of the corresponding language exists
+ Optional<Measure> rawMeasure = measureRepository.getRawMeasure(branchComponent, qualityProfileMetric);
+ if (rawMeasure.isEmpty()) {
+ // No qualify profile computed on the project
+ return;
+ }
+ Map<String, QualityProfile> rawProfiles = QPMeasureData.fromJson(rawMeasure.get().getStringValue()).getProfilesByKey();
+ detectIssueCapabilitiesChanged(rawProfiles.values());
+ }
+
+ private void detectIssueCapabilitiesChanged(Collection<QualityProfile> rawProfiles) {
+ Map<String, ScannerPlugin> scannerPluginsByKey = analysisMetadataHolder.getScannerPluginsByKey();
+ long lastAnalysisDate = requireNonNull(analysisMetadataHolder.getBaseAnalysis()).getCreatedAt();
+ for (QualityProfile profile : rawProfiles) {
+ String languageKey = profile.getLanguageKey();
+ ScannerPlugin scannerPlugin = scannerPluginsByKey.get(languageKey);
+ if (scannerPlugin == null) {
+ // nothing to do as the language is not supported
+ continue;
+ }
+
+ if (scannerPlugin.getUpdatedAt() > lastAnalysisDate) {
+ String languageName = languageRepository.find(languageKey)
+ .map(Language::getName)
+ .orElse(languageKey);
+ Event event = Event.createIssueDetection(format("Capabilities have been updated (%s)", languageName));
+ eventRepository.add(event);
+ }
+ }
+ }
+
+ @Override
+ public String getDescription() {
+ return "Generate Issue Detection events";
+ }
+}
}
private String convertCategory(Event.Category category) {
- switch (category) {
- case ALERT:
- return EventDto.CATEGORY_ALERT;
- case PROFILE:
- return EventDto.CATEGORY_PROFILE;
- default:
- throw new IllegalArgumentException(String.format("Unsupported category %s", category.name()));
- }
+ return switch (category) {
+ case ALERT -> EventDto.CATEGORY_ALERT;
+ case PROFILE -> EventDto.CATEGORY_PROFILE;
+ case ISSUE_DETECTION -> EventDto.CATEGORY_ISSUE_DETECTION;
+ };
}
}
// Must be executed after computation of quality gate measure
QualityGateEventsStep.class,
+ IssueDetectionEventsStep.class,
HandleUnanalyzedLanguagesStep.class,
--- /dev/null
+/*
+ * 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.ce.task.projectanalysis.step;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import javax.annotation.Nullable;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.resources.AbstractLanguage;
+import org.sonar.api.resources.Language;
+import org.sonar.ce.task.projectanalysis.analysis.Analysis;
+import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder;
+import org.sonar.ce.task.projectanalysis.analysis.ScannerPlugin;
+import org.sonar.ce.task.projectanalysis.component.Component;
+import org.sonar.ce.task.projectanalysis.component.ReportComponent;
+import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
+import org.sonar.ce.task.projectanalysis.event.Event;
+import org.sonar.ce.task.projectanalysis.event.EventRepository;
+import org.sonar.ce.task.projectanalysis.language.LanguageRepository;
+import org.sonar.ce.task.projectanalysis.measure.Measure;
+import org.sonar.ce.task.projectanalysis.measure.MeasureRepository;
+import org.sonar.ce.task.projectanalysis.metric.Metric;
+import org.sonar.ce.task.projectanalysis.metric.MetricRepository;
+import org.sonar.ce.task.step.TestComputationStepContext;
+import org.sonar.server.qualityprofile.QPMeasureData;
+import org.sonar.server.qualityprofile.QualityProfile;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.groups.Tuple.tuple;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+public class IssueDetectionEventsStepTest {
+ @Rule
+ public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
+
+ private static final String QP_NAME_1 = "qp_1";
+ private static final String QP_NAME_2 = "qp_2";
+ private static final String LANGUAGE_KEY_1 = "language_key1";
+ private static final String LANGUAGE_KEY_2 = "language_key2";
+
+ private final MetricRepository metricRepository = mock(MetricRepository.class);
+ private final MeasureRepository measureRepository = mock(MeasureRepository.class);
+ private final LanguageRepository languageRepository = mock(LanguageRepository.class);
+ private final EventRepository eventRepository = mock(EventRepository.class);
+ private final ArgumentCaptor<Event> eventArgumentCaptor = ArgumentCaptor.forClass(Event.class);
+ private final AnalysisMetadataHolder analysisMetadataHolder = mock(AnalysisMetadataHolder.class);
+
+ private final Metric qualityProfileMetric = mock(Metric.class);
+
+ private final IssueDetectionEventsStep underTest = new IssueDetectionEventsStep(treeRootHolder, metricRepository, measureRepository, languageRepository, eventRepository,
+ analysisMetadataHolder);
+
+ @Before
+ public void setUp() {
+ when(metricRepository.getByKey(CoreMetrics.QUALITY_PROFILES_KEY)).thenReturn(qualityProfileMetric);
+ treeRootHolder.setRoot(ReportComponent.builder(Component.Type.PROJECT, 1).setUuid("uuid").setKey("key").build());
+ }
+
+ @Test
+ public void execute_whenNoBaseMeasure_shouldNotRaiseEvent() {
+ when(measureRepository.getBaseMeasure(treeRootHolder.getRoot(), qualityProfileMetric)).thenReturn(Optional.empty());
+
+ underTest.execute(new TestComputationStepContext());
+
+ verifyNoMoreInteractions(eventRepository);
+ }
+
+ @Test
+ public void execute_whenNoRawMeasure_shouldNotRaiseEvent() {
+ when(measureRepository.getBaseMeasure(treeRootHolder.getRoot(), qualityProfileMetric)).thenReturn(Optional.of(newMeasure()));
+ when(measureRepository.getRawMeasure(treeRootHolder.getRoot(), qualityProfileMetric)).thenReturn(Optional.empty());
+
+ underTest.execute(new TestComputationStepContext());
+
+ verifyNoMoreInteractions(eventRepository);
+ }
+
+ @Test
+ public void execute_whenNoBaseMeasureAndQPMeasureIsEmpty_shouldNotRaiseEvent() {
+ when(measureRepository.getBaseMeasure(treeRootHolder.getRoot(), qualityProfileMetric)).thenReturn(Optional.empty());
+ when(measureRepository.getRawMeasure(treeRootHolder.getRoot(), qualityProfileMetric)).thenReturn(Optional.of(newMeasure()));
+
+ underTest.execute(new TestComputationStepContext());
+
+ verifyNoMoreInteractions(eventRepository);
+ }
+
+ @Test
+ public void execute_whenAnalyzerChangedAndLanguageNotSupported_shouldSkipRaisingEvent() {
+ QualityProfile qp1 = qp(QP_NAME_1, LANGUAGE_KEY_1, new Date());
+
+ mockLanguageInRepository(LANGUAGE_KEY_1);
+ when(measureRepository.getBaseMeasure(treeRootHolder.getRoot(), qualityProfileMetric)).thenReturn(Optional.of(newMeasure()));
+ when(measureRepository.getRawMeasure(treeRootHolder.getRoot(), qualityProfileMetric)).thenReturn(Optional.of(newMeasure(qp1)));
+
+ when(analysisMetadataHolder.getScannerPluginsByKey()).thenReturn(Collections.emptyMap());
+ when(analysisMetadataHolder.getBaseAnalysis()).thenReturn(new Analysis.Builder().setUuid("uuid").setCreatedAt(1L).build());
+
+ underTest.execute(new TestComputationStepContext());
+
+ verifyNoMoreInteractions(eventRepository);
+ }
+
+ @Test
+ public void execute_whenAnalyzerChanged_shouldRaiseEventForAllLanguages() {
+ QualityProfile qp1 = qp(QP_NAME_1, LANGUAGE_KEY_1, new Date());
+ QualityProfile qp2 = qp(QP_NAME_2, LANGUAGE_KEY_2, new Date());
+
+ mockLanguageInRepository(LANGUAGE_KEY_1);
+ mockLanguageInRepository(LANGUAGE_KEY_2);
+
+ when(measureRepository.getBaseMeasure(treeRootHolder.getRoot(), qualityProfileMetric)).thenReturn(Optional.of(newMeasure()));
+ when(measureRepository.getRawMeasure(treeRootHolder.getRoot(), qualityProfileMetric)).thenReturn(Optional.of(newMeasure(qp1, qp2)));
+
+ ScannerPlugin scannerPluginLanguage1 = mockScannerPlugin(LANGUAGE_KEY_1, 3L);
+ ScannerPlugin scannerPluginLanguage2 = mockScannerPlugin(LANGUAGE_KEY_2, 2L);
+
+ when(analysisMetadataHolder.getScannerPluginsByKey()).thenReturn(Map.of(LANGUAGE_KEY_1, scannerPluginLanguage1, LANGUAGE_KEY_2, scannerPluginLanguage2));
+ when(analysisMetadataHolder.getBaseAnalysis()).thenReturn(new Analysis.Builder().setUuid("uuid").setCreatedAt(1L).build());
+
+ underTest.execute(new TestComputationStepContext());
+
+ verify(eventRepository, times(2)).add(eventArgumentCaptor.capture());
+ verifyNoMoreInteractions(eventRepository);
+
+ assertThat(eventArgumentCaptor.getAllValues())
+ .extracting(Event::getCategory, Event::getName, Event::getDescription)
+ .containsExactlyInAnyOrder(tuple(Event.Category.ISSUE_DETECTION, "Capabilities have been updated (language_key1_name)", null),
+ tuple(Event.Category.ISSUE_DETECTION, "Capabilities have been updated (language_key2_name)", null));
+ }
+
+ @Test
+ public void execute_whenAnalyzerChangedAndAnalyzerUpdateDateBeforeAnalysis_shouldNotRaiseEvent() {
+ QualityProfile qp1 = qp(QP_NAME_1, LANGUAGE_KEY_1, new Date());
+
+ mockLanguageInRepository(LANGUAGE_KEY_1);
+
+ when(measureRepository.getBaseMeasure(treeRootHolder.getRoot(), qualityProfileMetric)).thenReturn(Optional.of(newMeasure()));
+ when(measureRepository.getRawMeasure(treeRootHolder.getRoot(), qualityProfileMetric)).thenReturn(Optional.of(newMeasure(qp1)));
+
+ ScannerPlugin scannerPluginLanguage1 = mockScannerPlugin(LANGUAGE_KEY_1, 1L);
+
+ when(analysisMetadataHolder.getScannerPluginsByKey()).thenReturn(Map.of(LANGUAGE_KEY_1, scannerPluginLanguage1));
+ when(analysisMetadataHolder.getBaseAnalysis()).thenReturn(new Analysis.Builder().setUuid("uuid").setCreatedAt(3L).build());
+
+ underTest.execute(new TestComputationStepContext());
+
+ verifyNoMoreInteractions(eventRepository);
+ }
+
+ @Test
+ public void execute_whenAnalyzerDidNotChange_shouldNotRaiseEvent() {
+ QualityProfile qp = qp(QP_NAME_1, LANGUAGE_KEY_1, new Date());
+
+ mockLanguage1AsNotInRepository();
+
+ when(analysisMetadataHolder.getBaseAnalysis()).thenReturn(new Analysis.Builder().setUuid("uuid").setCreatedAt(1L).build());
+
+ when(measureRepository.getBaseMeasure(treeRootHolder.getRoot(), qualityProfileMetric)).thenReturn(Optional.of(newMeasure()));
+ when(measureRepository.getRawMeasure(treeRootHolder.getRoot(), qualityProfileMetric)).thenReturn(Optional.of(newMeasure(qp)));
+
+ underTest.execute(new TestComputationStepContext());
+
+ verifyNoMoreInteractions(eventRepository);
+ }
+
+ private void mockLanguageInRepository(String languageKey) {
+ Language language = new AbstractLanguage(languageKey, languageKey + "_name") {
+ @Override
+ public String[] getFileSuffixes() {
+ return new String[0];
+ }
+ };
+ when(languageRepository.find(languageKey)).thenReturn(Optional.of(language));
+ }
+
+ private void mockLanguage1AsNotInRepository() {
+ when(languageRepository.find(LANGUAGE_KEY_1)).thenReturn(Optional.empty());
+ }
+
+ private static QualityProfile qp(String qpName, String languageKey, Date date) {
+ return new QualityProfile(qpName + "-" + languageKey, qpName, languageKey, date);
+ }
+
+ /**
+ * Just a trick to use variable args which is shorter than writing new QualityProfile[] { }
+ */
+ private static QualityProfile[] arrayOf(QualityProfile... qps) {
+ return qps;
+ }
+
+ private static Measure newMeasure(@Nullable QualityProfile... qps) {
+ return Measure.newMeasureBuilder().create(toJson(qps));
+ }
+
+ private static String toJson(@Nullable QualityProfile... qps) {
+ List<QualityProfile> qualityProfiles = qps != null ? Arrays.asList(qps) : Collections.emptyList();
+ return QPMeasureData.toJson(new QPMeasureData(qualityProfiles));
+ }
+
+ private static ScannerPlugin mockScannerPlugin(String repositoryKey, long updatedAt) {
+ ScannerPlugin scannerPluginLanguage = mock(ScannerPlugin.class);
+ when(scannerPluginLanguage.getUpdatedAt()).thenReturn(updatedAt);
+ when(scannerPluginLanguage.getKey()).thenReturn(repositoryKey);
+ return scannerPluginLanguage;
+ }
+}
public static final String CATEGORY_ALERT = "Alert";
public static final String CATEGORY_PROFILE = "Profile";
public static final String CATEGORY_DEFINITION_CHANGE = "Definition change";
-
+ public static final String CATEGORY_ISSUE_DETECTION = "Issue Detection";
private String uuid;
private String analysisUuid;
private String componentUuid;
package org.sonar.server.projectanalysis.ws;
public enum EventCategory {
- VERSION("Version"), OTHER("Other"), QUALITY_PROFILE("Profile"), QUALITY_GATE("Alert"), DEFINITION_CHANGE("Definition change");
+ VERSION("Version"),
+ OTHER("Other"),
+ QUALITY_PROFILE("Profile"),
+ QUALITY_GATE("Alert"),
+ DEFINITION_CHANGE("Definition change"),
+ ISSUE_DETECTION("Issue Detection");
private final String label;
event.category.QUALITY_GATE=Quality Gate
event.category.QUALITY_PROFILE=Quality Profile
event.category.DEFINITION_CHANGE=Definition Change
+event.category.ISSUE_DETECTION=Issue Detection
event.category.OTHER=Other
event.quality_gate.still_x=Still {status}
event.quality_gate.ERROR=Failed