aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-webserver-core
diff options
context:
space:
mode:
authorLukasz Jarocki <lukasz.jarocki@sonarsource.com>2022-02-28 09:27:47 +0100
committersonartech <sonartech@sonarsource.com>2022-03-02 20:03:47 +0000
commitbb4cc98d5f0b52c3caeb52fc4f772248be8d95fa (patch)
tree52bb9ec0e6368ef52c7d254ea6d06c4d9690e0f7 /server/sonar-webserver-core
parentc1db3d50a5d01d86417d40a45e0ecc769134f0c8 (diff)
downloadsonarqube-bb4cc98d5f0b52c3caeb52fc4f772248be8d95fa.tar.gz
sonarqube-bb4cc98d5f0b52c3caeb52fc4f772248be8d95fa.zip
SONAR-13704 activity of a project now also display QG changes caused by changing issues status
Diffstat (limited to 'server/sonar-webserver-core')
-rw-r--r--server/sonar-webserver-core/src/main/java/org/sonar/server/project/ProjectQGChangeEventListener.java103
-rw-r--r--server/sonar-webserver-core/src/test/java/org/sonar/server/project/ProjectQGChangeEventListenerTest.java156
2 files changed, 259 insertions, 0 deletions
diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/project/ProjectQGChangeEventListener.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/project/ProjectQGChangeEventListener.java
new file mode 100644
index 00000000000..15331a38c08
--- /dev/null
+++ b/server/sonar-webserver-core/src/main/java/org/sonar/server/project/ProjectQGChangeEventListener.java
@@ -0,0 +1,103 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.project;
+
+import java.util.Optional;
+import java.util.Set;
+import java.util.UUID;
+import javax.annotation.Nullable;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.utils.System2;
+import org.sonar.ce.task.projectanalysis.measure.Measure;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.SnapshotDto;
+import org.sonar.db.event.EventDto;
+import org.sonar.server.qualitygate.EvaluatedQualityGate;
+import org.sonar.server.qualitygate.changeevent.QGChangeEvent;
+import org.sonar.server.qualitygate.changeevent.QGChangeEventListener;
+
+import static org.sonar.db.event.EventDto.CATEGORY_ALERT;
+
+public class ProjectQGChangeEventListener implements QGChangeEventListener {
+
+ private final DbClient dbClient;
+
+ public ProjectQGChangeEventListener(DbClient dbClient) {
+ this.dbClient = dbClient;
+ }
+
+ @Override
+ public void onIssueChanges(QGChangeEvent qualityGateEvent, Set<ChangedIssue> changedIssues) {
+ Optional<EvaluatedQualityGate> evaluatedQualityGate = qualityGateEvent.getQualityGateSupplier().get();
+ Optional<Metric.Level> previousStatusOptional = qualityGateEvent.getPreviousStatus();
+ if (evaluatedQualityGate.isEmpty() || previousStatusOptional.isEmpty()) {
+ return;
+ }
+ Metric.Level currentStatus = evaluatedQualityGate.get().getStatus();
+ Metric.Level previousStatus = previousStatusOptional.get();
+ if (previousStatus.getColorName().equals(currentStatus.getColorName())) {
+ // QG status didn't change - no action
+ return;
+ }
+
+ addQualityGateEventToProject(qualityGateEvent, currentStatus);
+ }
+
+ private void addQualityGateEventToProject(QGChangeEvent qualityGateEvent, Metric.Level currentStatus) {
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ String componentUuid = qualityGateEvent.getAnalysis().getComponentUuid();
+
+ SnapshotDto liveMeasureSnapshotDto = createLiveMeasureSnapshotDto(qualityGateEvent.getAnalysis().getProjectVersion(), componentUuid);
+ dbClient.snapshotDao().insert(dbSession, liveMeasureSnapshotDto);
+
+ EventDto eventDto = createEventDto(componentUuid, liveMeasureSnapshotDto.getUuid(), currentStatus);
+ dbClient.eventDao().insert(dbSession, eventDto);
+
+ dbSession.commit();
+ }
+ }
+
+ private static EventDto createEventDto(String componentUuid, String snapshotUuid, Metric.Level currentStatus) {
+ EventDto eventDto = new EventDto();
+ eventDto.setComponentUuid(componentUuid);
+ eventDto.setCreatedAt(System2.INSTANCE.now());
+ eventDto.setCategory(CATEGORY_ALERT);
+ eventDto.setDate(System2.INSTANCE.now());
+ eventDto.setAnalysisUuid(snapshotUuid);
+ eventDto.setUuid(UUID.randomUUID().toString());
+ eventDto.setName(currentStatus.getColorName().equals(Metric.Level.OK.getColorName()) ? Measure.Level.OK.getLabel() : Measure.Level.ERROR.getLabel());
+
+ return eventDto;
+ }
+
+ private static SnapshotDto createLiveMeasureSnapshotDto(@Nullable String projectVersion, String componentUuid) {
+ SnapshotDto dto = new SnapshotDto();
+
+ dto.setUuid(UUID.randomUUID().toString());
+ dto.setCreatedAt(System2.INSTANCE.now());
+ dto.setProjectVersion(projectVersion);
+ dto.setLast(false);
+ dto.setStatus(SnapshotDto.STATUS_LIVE_MEASURE_COMPUTED);
+ dto.setComponentUuid(componentUuid);
+
+ return dto;
+ }
+}
diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/project/ProjectQGChangeEventListenerTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/project/ProjectQGChangeEventListenerTest.java
new file mode 100644
index 00000000000..f85417da4ea
--- /dev/null
+++ b/server/sonar-webserver-core/src/test/java/org/sonar/server/project/ProjectQGChangeEventListenerTest.java
@@ -0,0 +1,156 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.project;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Supplier;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.config.Configuration;
+import org.sonar.api.measures.Metric;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.BranchDto;
+import org.sonar.db.component.ComponentDao;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.SnapshotDao;
+import org.sonar.db.component.SnapshotDto;
+import org.sonar.db.event.EventDao;
+import org.sonar.db.project.ProjectDto;
+import org.sonar.server.qualitygate.Condition;
+import org.sonar.server.qualitygate.EvaluatedCondition;
+import org.sonar.server.qualitygate.EvaluatedQualityGate;
+import org.sonar.server.qualitygate.QualityGate;
+import org.sonar.server.qualitygate.changeevent.QGChangeEvent;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+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.mockito.Mockito.when;
+
+public class ProjectQGChangeEventListenerTest {
+
+ private final DbClient dbClient = mock(DbClient.class);
+
+ private final SnapshotDao snapshotDao = mock(SnapshotDao.class);
+ private final EventDao eventDao = mock(EventDao.class);
+ private final ComponentDao componentDao = mock(ComponentDao.class);
+
+ private final Configuration projectConfiguration = mock(Configuration.class);
+ private final BranchDto branch = new BranchDto();
+ private final Condition defaultCondition = new Condition("bugs", Condition.Operator.GREATER_THAN, "10");
+
+ private final ProjectQGChangeEventListener underTest = new ProjectQGChangeEventListener(dbClient);
+
+ private ProjectDto project;
+ private SnapshotDto analysis;
+
+ @Before
+ public void before() {
+ project = new ProjectDto();
+ project.setUuid("uuid");
+ analysis = new SnapshotDto();
+
+ when(dbClient.componentDao()).thenReturn(componentDao);
+ when(dbClient.eventDao()).thenReturn(eventDao);
+ when(dbClient.snapshotDao()).thenReturn(snapshotDao);
+
+ when(dbClient.openSession(false)).thenReturn(mock(DbSession.class));
+ }
+
+ @Test
+ public void onIssueChanges_givenEmptyEvent_doNotInteractWithDatabase() {
+ Supplier<Optional<EvaluatedQualityGate>> qualityGateSupplier = Optional::empty;
+
+ QGChangeEvent qualityGateEvent = new QGChangeEvent(project, branch, analysis, projectConfiguration, Metric.Level.OK, qualityGateSupplier);
+
+ underTest.onIssueChanges(qualityGateEvent, Set.of());
+
+ verifyNoInteractions(dbClient);
+ }
+
+ @Test
+ public void onIssueChanges_givenEventWithNoPreviousStatus_doNotInteractWithDatabase() {
+ EvaluatedQualityGate value = EvaluatedQualityGate.newBuilder()
+ .setQualityGate(createDefaultQualityGate())
+ .addEvaluatedCondition(createEvaluatedCondition())
+ .setStatus(Metric.Level.ERROR)
+ .build();
+ Supplier<Optional<EvaluatedQualityGate>> qualityGateSupplier = () -> Optional.of(value);
+
+ QGChangeEvent qualityGateEvent = new QGChangeEvent(project, branch, analysis, projectConfiguration, null, qualityGateSupplier);
+
+ underTest.onIssueChanges(qualityGateEvent, Set.of());
+
+ verifyNoInteractions(dbClient);
+ }
+
+ @Test
+ public void onIssueChanges_givenCurrentStatusTheSameAsPrevious_doNotInteractWithDatabase() {
+ EvaluatedQualityGate value = EvaluatedQualityGate.newBuilder()
+ .setQualityGate(createDefaultQualityGate())
+ .addEvaluatedCondition(createEvaluatedCondition())
+ .setStatus(Metric.Level.ERROR)
+ .build();
+ Supplier<Optional<EvaluatedQualityGate>> qualityGateSupplier = () -> Optional.of(value);
+
+ QGChangeEvent qualityGateEvent = new QGChangeEvent(project, branch, analysis, projectConfiguration, Metric.Level.ERROR, qualityGateSupplier);
+
+ underTest.onIssueChanges(qualityGateEvent, Set.of());
+
+ verifyNoInteractions(dbClient);
+ }
+
+ @Test
+ public void onIssueChanges_whenValidEvent_insertEventAndSnapshotToDatabase() {
+ EvaluatedQualityGate value = EvaluatedQualityGate.newBuilder()
+ .setQualityGate(createDefaultQualityGate())
+ .addEvaluatedCondition(createEvaluatedCondition())
+ .setStatus(Metric.Level.OK)
+ .build();
+ Supplier<Optional<EvaluatedQualityGate>> qualityGateSupplier = () -> Optional.of(value);
+
+ QGChangeEvent qualityGateEvent = new QGChangeEvent(project, branch, analysis, projectConfiguration, Metric.Level.ERROR, qualityGateSupplier);
+
+ ComponentDto projectComponentDto = new ComponentDto();
+ projectComponentDto.setQualifier("TRK");
+ projectComponentDto.setUuid("uuid");
+ when(componentDao.selectByProjectUuid(anyString(), any())).thenReturn(List.of(projectComponentDto));
+
+ underTest.onIssueChanges(qualityGateEvent, Set.of());
+
+ verify(eventDao, times(1)).insert(any(), any());
+ verify(snapshotDao, times(1)).insert(any(DbSession.class), any(SnapshotDto.class));
+ }
+
+ private EvaluatedCondition createEvaluatedCondition() {
+ return new EvaluatedCondition(defaultCondition, EvaluatedCondition.EvaluationStatus.ERROR, "5");
+ }
+
+ private QualityGate createDefaultQualityGate() {
+ return new QualityGate("id", "name", Set.of(defaultCondition));
+ }
+
+}