From 10baf0439cac6c9ebc096f86231b8e194841aa15 Mon Sep 17 00:00:00 2001 From: Matteo Mara Date: Thu, 5 Oct 2023 10:11:53 +0200 Subject: [PATCH] SONAR-20667 Add a specific event to the first analysis after a SQ upgrade --- .../step/PersistEventsStepIT.java | 31 +++++ .../ce/task/projectanalysis/event/Event.java | 6 +- .../step/PersistEventsStep.java | 1 + .../step/ReportComputationSteps.java | 1 + .../step/SqUpgradeDetectionEventsStep.java | 74 +++++++++++ .../task/projectanalysis/event/EventTest.java | 9 ++ .../SqUpgradeDetectionEventsStepTest.java | 122 ++++++++++++++++++ .../java/org/sonar/db/event/EventDaoIT.java | 18 +++ .../java/org/sonar/db/event/EventDao.java | 4 + .../java/org/sonar/db/event/EventDto.java | 1 + .../java/org/sonar/db/event/EventMapper.java | 2 + .../org/sonar/db/event/EventMapper.xml | 14 ++ .../projectanalysis/ws/EventCategory.java | 3 +- 13 files changed, 284 insertions(+), 2 deletions(-) create mode 100644 server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/SqUpgradeDetectionEventsStep.java create mode 100644 server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/SqUpgradeDetectionEventsStepTest.java diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistEventsStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistEventsStepIT.java index 348b0bf02a6..c2b31e821a5 100644 --- a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistEventsStepIT.java +++ b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistEventsStepIT.java @@ -34,6 +34,7 @@ import org.sonar.ce.task.projectanalysis.event.Event; import org.sonar.ce.task.projectanalysis.event.EventRepository; import org.sonar.ce.task.step.ComputationStep; import org.sonar.ce.task.step.TestComputationStepContext; +import org.sonar.core.platform.SonarQubeVersion; import org.sonar.core.util.UuidFactory; import org.sonar.core.util.UuidFactoryImpl; import org.sonar.db.DbTester; @@ -51,6 +52,7 @@ import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builde 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_SQ_UPGRADE; import static org.sonar.db.event.EventDto.CATEGORY_VERSION; public class PersistEventsStepIT extends BaseStepTest { @@ -88,6 +90,8 @@ public class PersistEventsStepIT extends BaseStepTest { private final EventRepository eventRepository = mock(EventRepository.class); private final UuidFactory uuidFactory = UuidFactoryImpl.INSTANCE; + private final SonarQubeVersion sonarQubeVersion = mock(); + private PersistEventsStep underTest; @Before @@ -213,6 +217,33 @@ public class PersistEventsStepIT extends BaseStepTest { assertThat(eventDto.getCreatedAt()).isEqualTo(NOW); } + @Test + public void execute_whenSqUpgradeEventRaised_shouldPersist() { + when(system2.now()).thenReturn(NOW); + treeRootHolder.setRoot(ROOT); + Event sqUpgradeEvent = Event.createSqUpgrade("10.3"); + when(eventRepository.getEvents()).thenReturn(List.of(sqUpgradeEvent)); + + underTest.execute(new TestComputationStepContext()); + + assertThat(dbTester.countRowsOfTable(dbTester.getSession(), "events")).isEqualTo(2); + List eventDtos = dbTester.getDbClient().eventDao().selectByComponentUuid(dbTester.getSession(), ROOT.getUuid()); + assertThat(eventDtos) + .extracting(EventDto::getCategory) + .containsOnly(CATEGORY_SQ_UPGRADE, CATEGORY_VERSION); + EventDto eventDto = eventDtos.stream() + .filter(t -> CATEGORY_SQ_UPGRADE.equals(t.getCategory())) + .findAny() + .orElseGet(() -> fail("Issue detection event not found")); + assertThat(eventDto.getComponentUuid()).isEqualTo(ROOT.getUuid()); + assertThat(eventDto.getName()).isEqualTo(sqUpgradeEvent.getName()); + assertThat(eventDto.getCategory()).isEqualTo(CATEGORY_SQ_UPGRADE); + assertThat(eventDto.getDescription()).isNull(); + 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(); diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/event/Event.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/event/Event.java index 1b2ccff2f89..0eed401454d 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/event/Event.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/event/Event.java @@ -60,6 +60,10 @@ public class Event { return new Event(name, Category.ISSUE_DETECTION); } + public static Event createSqUpgrade(String name) { + return new Event(name, Category.SQ_UPGRADE); + } + public String getName() { return name; } @@ -96,7 +100,7 @@ public class Event { } public enum Category { - ALERT, PROFILE, ISSUE_DETECTION + ALERT, PROFILE, ISSUE_DETECTION, SQ_UPGRADE } } diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/PersistEventsStep.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/PersistEventsStep.java index 711427f678d..bee3402d951 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/PersistEventsStep.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/PersistEventsStep.java @@ -123,6 +123,7 @@ public class PersistEventsStep implements ComputationStep { case ALERT -> EventDto.CATEGORY_ALERT; case PROFILE -> EventDto.CATEGORY_PROFILE; case ISSUE_DETECTION -> EventDto.CATEGORY_ISSUE_DETECTION; + case SQ_UPGRADE -> EventDto.CATEGORY_SQ_UPGRADE; }; } diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/ReportComputationSteps.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/ReportComputationSteps.java index 731935a5425..de6e12dc657 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/ReportComputationSteps.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/ReportComputationSteps.java @@ -53,6 +53,7 @@ public class ReportComputationSteps extends AbstractComputationSteps { // Pre analysis operations PreMeasuresComputationChecksStep.class, + SqUpgradeDetectionEventsStep.class, // load project related stuffs LoadFileHashesAndStatusStep.class, diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/SqUpgradeDetectionEventsStep.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/SqUpgradeDetectionEventsStep.java new file mode 100644 index 00000000000..fbf79c04fc3 --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/SqUpgradeDetectionEventsStep.java @@ -0,0 +1,74 @@ +/* + * 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.List; +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.step.ComputationStep; +import org.sonar.core.platform.SonarQubeVersion; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.event.EventDto; + +/** + * Computation of SQ Upgrade events + */ +public class SqUpgradeDetectionEventsStep implements ComputationStep { + private final TreeRootHolder treeRootHolder; + private final DbClient dbClient; + private final EventRepository eventRepository; + private final SonarQubeVersion sonarQubeVersion; + + public SqUpgradeDetectionEventsStep(TreeRootHolder treeRootHolder, DbClient dbClient, + EventRepository eventRepository, SonarQubeVersion sonarQubeVersion) { + this.treeRootHolder = treeRootHolder; + this.dbClient = dbClient; + this.eventRepository = eventRepository; + this.sonarQubeVersion = sonarQubeVersion; + } + + @Override + public void execute(Context context) { + executeForBranch(treeRootHolder.getRoot()); + } + + private void executeForBranch(Component branchComponent) { + String currentSqVersion = sonarQubeVersion.get().toString(); + try (DbSession dbSession = dbClient.openSession(false)) { + List sqUpgradeEvents = dbClient.eventDao().selectSqUpgradesByMostRecentFirst(dbSession, branchComponent.getUuid()); + + //We don't really care about newer versions, we want to log the events related to a version change. + boolean isFirstAnalysisForSqVersion = sqUpgradeEvents.isEmpty() || !currentSqVersion.equals(sqUpgradeEvents.get(0).getName()); + + if (isFirstAnalysisForSqVersion) { + Event event = Event.createSqUpgrade(currentSqVersion); + eventRepository.add(event); + } + } + } + + @Override + public String getDescription() { + return "Generate SQ Upgrade analysis events"; + } +} diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/event/EventTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/event/EventTest.java index 264f14d2cc2..d8b7b997781 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/event/EventTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/event/EventTest.java @@ -68,4 +68,13 @@ public class EventTest { .isEqualTo(source) .isNotNull(); } + + @Test + public void createSqUpgradeEvents_verify_fields() { + Event event = Event.createSqUpgrade(SOME_NAME); + assertThat(event.getName()).isEqualTo(SOME_NAME); + assertThat(event.getCategory()).isEqualTo(Event.Category.SQ_UPGRADE); + assertThat(event.getData()).isNull(); + assertThat(event.getDescription()).isNull(); + } } diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/SqUpgradeDetectionEventsStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/SqUpgradeDetectionEventsStepTest.java new file mode 100644 index 00000000000..0eba6dd0769 --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/SqUpgradeDetectionEventsStepTest.java @@ -0,0 +1,122 @@ +/* + * 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.List; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.sonar.api.utils.Version; +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.step.TestComputationStepContext; +import org.sonar.core.platform.SonarQubeVersion; +import org.sonar.db.DbClient; +import org.sonar.db.event.EventDto; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.groups.Tuple.tuple; +import static org.mockito.ArgumentMatchers.any; +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 SqUpgradeDetectionEventsStepTest { + + @Rule + public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule(); + + private final DbClient dbClient = mock(); + private final EventRepository eventRepository = mock(); + + private final ArgumentCaptor eventArgumentCaptor = ArgumentCaptor.forClass(Event.class); + + private final SonarQubeVersion sonarQubeVersion = mock(); + + private final SqUpgradeDetectionEventsStep underTest = new SqUpgradeDetectionEventsStep(treeRootHolder, dbClient, eventRepository, sonarQubeVersion); + + @Before + public void setUp() { + treeRootHolder.setRoot(ReportComponent.builder(Component.Type.PROJECT, 1).setUuid("uuid").setKey("key").build()); + } + + @Test + public void givenNoPreviousUpgradeEvents_whenStepIsExecuted_thenANewUpgradeEventIsCreated() { + when(sonarQubeVersion.get()).thenReturn(Version.parse("10.3")); + when(dbClient.eventDao()).thenReturn(mock()); + when(dbClient.eventDao().selectSqUpgradesByMostRecentFirst(any(), any())).thenReturn(Collections.emptyList()); + + underTest.execute(new TestComputationStepContext()); + + verify(eventRepository, times(1)).add(eventArgumentCaptor.capture()); + verifyNoMoreInteractions(eventRepository); + + assertThat(eventArgumentCaptor.getAllValues()) + .extracting(Event::getCategory, Event::getName) + .containsExactly(tuple(Event.Category.SQ_UPGRADE, "10.3")); + } + + @Test + public void givenUpgradeEventWithTheSameSqVersion_whenStepIsExecuted_thenNothingIsPersisted() { + when(sonarQubeVersion.get()).thenReturn(Version.parse("10.3")); + when(dbClient.eventDao()).thenReturn(mock()); + when(dbClient.eventDao().selectSqUpgradesByMostRecentFirst(any(), any())).thenReturn(getUpgradeEvents("10.3", "10.2")); + + underTest.execute(new TestComputationStepContext()); + + verifyNoMoreInteractions(eventRepository); + } + + private List getUpgradeEvents(String... versions) { + return Arrays.stream(versions) + .map(version -> new EventDto().setCategory(EventDto.CATEGORY_SQ_UPGRADE).setName(version)) + .toList(); + } + + @Test + public void givenUpgradeEventWithDifferentSqVersion_whenStepIsExecuted_thenANewUpgradeEventIsCreated() { + when(sonarQubeVersion.get()).thenReturn(Version.parse("10.3")); + when(dbClient.eventDao()).thenReturn(mock()); + when(dbClient.eventDao().selectSqUpgradesByMostRecentFirst(any(), any())).thenReturn(getUpgradeEvents("10.2", "10.1")); + + underTest.execute(new TestComputationStepContext()); + + verify(eventRepository, times(1)).add(eventArgumentCaptor.capture()); + verifyNoMoreInteractions(eventRepository); + + assertThat(eventArgumentCaptor.getAllValues()) + .extracting(Event::getCategory, Event::getName) + .containsExactly(tuple(Event.Category.SQ_UPGRADE, "10.3")); + } + + @Test + public void whenGetDescriptionIsCalled_shouldReturnExpectedValue() { + assertThat(underTest.getDescription()).isEqualTo("Generate SQ Upgrade analysis events"); + } + +} diff --git a/server/sonar-db-dao/src/it/java/org/sonar/db/event/EventDaoIT.java b/server/sonar-db-dao/src/it/java/org/sonar/db/event/EventDaoIT.java index 3501b5dcf2d..05d57208c36 100644 --- a/server/sonar-db-dao/src/it/java/org/sonar/db/event/EventDaoIT.java +++ b/server/sonar-db-dao/src/it/java/org/sonar/db/event/EventDaoIT.java @@ -199,6 +199,24 @@ public class EventDaoIT { assertThat(result.getDescription()).isEqualTo("New Description"); } + @Test + public void givenSomeSqUpgradeEvents_whenRetrieved_shouldReturnCorrectlyOrderedByDateDescending() { + long olderDate = 1L; + long newerDate = 2L; + + ComponentDto componentDto = ComponentTesting.newPrivateProjectDto(); + SnapshotDto analysis = dbTester.components().insertProjectAndSnapshot(componentDto); + dbTester.events().insertEvent(newEvent(analysis).setCategory(EventDto.CATEGORY_SQ_UPGRADE).setDate(olderDate).setUuid("E1")); + dbTester.events().insertEvent(newEvent(analysis).setCategory(EventDto.CATEGORY_SQ_UPGRADE).setDate(newerDate).setUuid("E2")); + + List events = underTest.selectSqUpgradesByMostRecentFirst(dbSession, componentDto.uuid()); + assertThat(events).hasSize(2); + assertThat(events.get(0).getUuid()).isEqualTo("E2"); + assertThat(events.get(0).getDate()).isEqualTo(newerDate); + assertThat(events.get(1).getUuid()).isEqualTo("E1"); + assertThat(events.get(1).getDate()).isEqualTo(olderDate); + } + @Test public void delete_by_uuid() { dbTester.events().insertEvent(newEvent(newAnalysis(ComponentTesting.newPrivateProjectDto())).setUuid("E1")); diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/event/EventDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/event/EventDao.java index 49f2d0cf2a1..598c73e1ff4 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/event/EventDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/event/EventDao.java @@ -49,6 +49,10 @@ public class EventDao implements Dao { return mapper(session).selectVersions(componentUuid); } + public List selectSqUpgradesByMostRecentFirst(DbSession session, String componentUuid) { + return mapper(session).selectSqUpgrades(componentUuid); + } + public EventDto insert(DbSession session, EventDto dto) { mapper(session).insert(dto); diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/event/EventDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/event/EventDto.java index c03d11d7cf6..e54ded6a98e 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/event/EventDto.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/event/EventDto.java @@ -33,6 +33,7 @@ public class EventDto { 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"; + public static final String CATEGORY_SQ_UPGRADE = "SQ Upgrade"; private String uuid; private String analysisUuid; private String componentUuid; diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/event/EventMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/event/EventMapper.java index ddb8becc86f..63c18c821c4 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/event/EventMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/event/EventMapper.java @@ -35,6 +35,8 @@ public interface EventMapper { List selectVersions(@Param("componentUuid") String componentUuid); + List selectSqUpgrades(@Param("componentUuid") String componentUuid); + void insert(EventDto dto); void update(@Param("uuid") String uuid, @Param("name") @Nullable String name, @Param("description") @Nullable String description); diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/event/EventMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/event/EventMapper.xml index c367b2a7d07..e5ada5f0c30 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/event/EventMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/event/EventMapper.xml @@ -64,6 +64,20 @@ e.event_date desc + + INSERT INTO events (uuid, analysis_uuid, component_uuid, name, category, description, event_data, event_date, created_at) VALUES ( diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/projectanalysis/ws/EventCategory.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/projectanalysis/ws/EventCategory.java index 1e3371820e1..77c506e9d7c 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/projectanalysis/ws/EventCategory.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/projectanalysis/ws/EventCategory.java @@ -25,7 +25,8 @@ public enum EventCategory { QUALITY_PROFILE("Profile"), QUALITY_GATE("Alert"), DEFINITION_CHANGE("Definition change"), - ISSUE_DETECTION("Issue Detection"); + ISSUE_DETECTION("Issue Detection"), + SQ_UPGRADE("SQ Upgrade"); private final String label; -- 2.39.5